网络流从入门到入土 #2
这份题单UVA的题目偏多。但是洛谷不知道为啥UVA交不上去。所以一般我都是去VJ上刷题。
较为简单的最大流模板题:
The Grand Dinner
这道题就是上一份题单里的圆桌问题。改改输出就好了。
#pragma GCC optimize(2)
#include <bits/stdc++.h>
using namespace std;
#define pii pair<long long, int>
typedef long long ll;
const int maxn = 1e6 + 10;
const int maxm = 1e5 * 2 + 10;
const ll inf = 1e18;
const int mod = 1e9 + 7;
struct Edge{
int to;
int next;
ll w;
} edge[maxn*2];
int head[maxn], node_num;
int cur[maxn];//
int n, m, S, T, k;//
int dis[maxn];//Bf
int cnt = 0;//
bool vis[maxn];
inline void init(){
cnt = 0;
for (int i = 0; i <= node_num; ++ i) head[i] = -1;
}
inline void Add_Edge(int u, int v, ll w){
edge[cnt].next = head[u];
edge[cnt].to = v;
edge[cnt].w = w;
head[u] = cnt++;
edge[cnt].next = head[v];
edge[cnt].to = u;
edge[cnt].w = 0;
head[v] = cnt++;
}
inline bool Bfs(){
for(int i = 0; i <= node_num; ++i) dis[i] = -1;
dis[S] = 0;
queue<int> q;
q.push(S);
while(!q.empty()){
int u = q.front(); q.pop();
for(int i = head[u]; i != -1; i = edge[i].next){
int v = edge[i].to;
if(dis[v] == -1 && edge[i].w){
dis[v] = dis[u] + 1;
q.push(v);
}
}
}
return dis[T] != -1;
}
inline ll dfs(int u, ll flow){
if(u == T) return flow;
ll del = flow;
for(int i = cur[u]; i != -1; i = edge[i].next){
cur[u] = edge[i].next;
int v = edge[i].to;
if (dis[v] == dis[u] + 1 && edge[i].w > 0){
ll ans = dfs(v, min(del, edge[i].w));
edge[i].w -= ans;
edge[i ^ 1].w += ans;
del -= ans;
if(del == 0) break;
}
}
return flow - del;
}
inline ll Dinic() {
ll ans = 0;
while(Bfs()) {
for(int i = 0; i <= node_num; ++i) cur[i] = head[i];
ans += dfs(S, inf);
}
return ans;
}
int main() {
while (~scanf("%d %d", &m, &n))
{
if (!m && !n) break;
S = 0, T = n+m+1;
node_num = T;
init();
int rec_n[n+1], rec_m[m+1];
int sum = 0;
for (int i = 1; i <= m; ++ i) {
scanf("%d", &rec_m[i]);
sum += rec_m[i];
Add_Edge(S, i, rec_m[i]);
for (int j = 1; j <= n; ++ j)
Add_Edge(i, j+m, 1);
}
for (int i = 1; i <= n; ++ i) {
scanf("%d", &rec_n[i]);
Add_Edge(i+m, T, rec_n[i]);
}
int flag = 0;
if (Dinic() == sum) flag = 1;
cout << flag << endl;
if (flag) {
vector<int> rec[m+1];
for (int u = 1; u <= m; ++ u) {
for (int j = head[u]; ~j; j = edge[j].next) {
if (edge[j].w == 0) {
rec[u].push_back(edge[j].to-m);
}
}
}
for (int i = 1; i <= m; ++ i) {
cout << rec[i][0];
for (int j = 1; j < rec[i].size(); ++ j)
cout << " " << rec[i][j];
cout << endl;
}
}
}
return 0;
}
View Code
Crimewave
题意:给你n个点,问你是否存在n条连向矩阵外的不相交路径。
做法:将每个点拆成两个点。流量为1,能到矩阵外的点连T,开始n个点连S;跑最大流判断是否等于n即可。
#pragma GCC optimize(2)
#include <bits/stdc++.h>
using namespace std;
#define pii pair<long long, int>
typedef long long ll;
const int maxn = 1e6 + 10;
const int maxm = 1e5 * 2 + 10;
const ll inf = 1e18;
const int mod = 1e9 + 7;
struct Edge{
int to;
int next;
ll w;
} edge[maxn*2];
int head[maxn], node_num;
int cur[maxn];//当前弧优化数组
int n, m, S, T, k;//点数,边数,源点,汇点
int dis[maxn];//Bfs深度
int cnt = 0;//边数
bool vis[maxn];
inline void init(){
cnt = 0;
for (int i = 0; i <= node_num; ++ i) head[i] = -1;
}
inline void Add_Edge(int u, int v, ll w){
edge[cnt].next = head[u];
edge[cnt].to = v;
edge[cnt].w = w;
head[u] = cnt++;
edge[cnt].next = head[v];
edge[cnt].to = u;
edge[cnt].w = 0;
head[v] = cnt++;
}
inline bool Bfs(){
for(int i = 0; i <= node_num; ++i) dis[i] = -1; //这里要根据你所建的点数来确定
dis[S] = 0;
queue<int> q;
q.push(S);
while(!q.empty()){
int u = q.front(); q.pop();
for(int i = head[u]; i != -1; i = edge[i].next){
int v = edge[i].to;
if(dis[v] == -1 && edge[i].w){//没有标记深度并且有残量
dis[v] = dis[u] + 1;
q.push(v);
}
}
}
return dis[T] != -1;
}
inline ll dfs(int u, ll flow){
if(u == T) return flow;
ll del = flow;
for(int i = cur[u]; i != -1; i = edge[i].next){
cur[u] = edge[i].next;//当前弧优化,下次直接从cur[u]开始增广,节省时间
int v = edge[i].to;
if (dis[v] == dis[u] + 1 && edge[i].w > 0){//深度+1且残量大于0
ll ans = dfs(v, min(del, edge[i].w));//木桶原理
edge[i].w -= ans;//正向弧减增广流量
edge[i ^ 1].w += ans;//反向弧加增广流量
del -= ans;//总流量减增广流量
if(del == 0) break;//总流量为0则不继续增广
}
}
return flow - del;//返回本次增广的流量
}
inline ll Dinic() {
ll ans = 0;
while(Bfs()) {
for(int i = 0; i <= node_num; ++i) cur[i] = head[i];
ans += dfs(S, inf);
}
return ans;
}
int dic[4][2] = {1,0, -1,0, 0,-1, 0,1};
int main() {
int Ti;
scanf("%d", &Ti);
while (Ti--)
{
scanf("%d %d %d", &n, &m, &k);
S = 0, T = 2*n*m+1;
node_num = T;
init();
int sum = 0;
for (int i = 1, u, v; i <= k; ++ i) {
scanf("%d %d", &u, &v);
int id = (u-1)*m+v;
Add_Edge(S, id, inf);
}
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j <= m; ++ j) {
int id = (i-1)*m+j;
Add_Edge(id, id+n*m, 1);
for (int k = 0; k < 4; ++ k) {
int tx = i + dic[k][0], ty = j + dic[k][1];
int id1 = (tx-1)*m+ty;
if (tx < 1 || ty < 1 || tx > n || ty > m) {
Add_Edge(id+n*m, T, inf);
continue;
}
Add_Edge(id+n*m, id1, inf);
}
}
}
if (Dinic() == k) cout << "possible" << endl;
else cout << "not possible" << endl;
}
return 0;
}
View Code
Internet Bandwidth
题目大意:单源单汇无向网络求最大流。
#pragma GCC optimize(2)
#include <bits/stdc++.h>
using namespace std;
#define pii pair<long long, int>
typedef long long ll;
const int maxn = 1e6 + 10;
const int maxm = 1e5 * 2 + 10;
const ll inf = 1e18;
const int mod = 1e9 + 7;
struct Edge{
int to;
int next;
ll w;
} edge[maxn*2];
int head[maxn], node_num;
int cur[maxn];//当前弧优化数组
int n, m, S, T, k;//点数,边数,源点,汇点
int dis[maxn];//Bfs深度
int cnt = 0;//边数
bool vis[maxn];
inline void init(){
cnt = 0;
for (int i = 0; i <= node_num; ++ i) head[i] = -1;
}
inline void Add_Edge(int u, int v, ll w){
edge[cnt].next = head[u];
edge[cnt].to = v;
edge[cnt].w = w;
head[u] = cnt++;
edge[cnt].next = head[v];
edge[cnt].to = u;
edge[cnt].w = 0;
head[v] = cnt++;
}
inline bool Bfs(){
for(int i = 0; i <= node_num; ++i) dis[i] = -1; //这里要根据你所建的点数来确定
dis[S] = 0;
queue<int> q;
q.push(S);
while(!q.empty()){
int u = q.front(); q.pop();
for(int i = head[u]; i != -1; i = edge[i].next){
int v = edge[i].to;
if(dis[v] == -1 && edge[i].w){//没有标记深度并且有残量
dis[v] = dis[u] + 1;
q.push(v);
}
}
}
return dis[T] != -1;
}
inline ll dfs(int u, ll flow){
if(u == T) return flow;
ll del = flow;
for(int i = cur[u]; i != -1; i = edge[i].next){
cur[u] = edge[i].next;//当前弧优化,下次直接从cur[u]开始增广,节省时间
int v = edge[i].to;
if (dis[v] == dis[u] + 1 && edge[i].w > 0){//深度+1且残量大于0
ll ans = dfs(v, min(del, edge[i].w));//木桶原理
edge[i].w -= ans;//正向弧减增广流量
edge[i ^ 1].w += ans;//反向弧加增广流量
del -= ans;//总流量减增广流量
if(del == 0) break;//总流量为0则不继续增广
}
}
return flow - del;//返回本次增广的流量
}
inline ll Dinic() {
ll ans = 0;
while(Bfs()) {
for(int i = 0; i <= node_num; ++i) cur[i] = head[i];
ans += dfs(S, inf);
}
return ans;
}
int dic[4][2] = {1,0, -1,0, 0,-1, 0,1};
int main() {
int tot = 0;
while (~scanf("%d", &n) && n) {
scanf("%d %d %d", &S, &T, &m);
//S = 0, T = 2*n*m+1;
node_num = n;
init();
int sum = 0;
for (int i = 1, u, v, cap; i <= m; ++ i) {
scanf("%d %d %d", &u, &v, &cap);
Add_Edge(u, v, cap), Add_Edge(v, u, cap);
}
//cout << Dinic() << endl;
cout << "Network " << ++tot << endl;
cout << "The bandwidth is " << Dinic() << "." << endl << endl;
}
return 0;
}
View Code
Air Raid
有向无环图(DAG)的最小路径覆盖的模板题。
#pragma GCC optimize(2)
#include <bits/stdc++.h>
using namespace std;
#define pii pair<long long, int>
typedef long long ll;
const int maxn = 1e6 + 10;
const int maxm = 1e5 * 2 + 10;
const ll inf = 1e18;
const int mod = 1e9 + 7;
struct Edge{
int to;
int next;
ll w;
} edge[maxn*2];
int head[maxn], node_num;
int cur[maxn];//当前弧优化数组
int n, m, S, T, k;//点数,边数,源点,汇点
int dis[maxn];//Bfs深度
int cnt = 0;//边数
bool vis[maxn];
inline void init(){
cnt = 0;
for (int i = 0; i <= node_num; ++ i) head[i] = -1;
}
inline void Add_Edge(int u, int v, ll w){
edge[cnt].next = head[u];
edge[cnt].to = v;
edge[cnt].w = w;
head[u] = cnt++;
edge[cnt].next = head[v];
edge[cnt].to = u;
edge[cnt].w = 0;
head[v] = cnt++;
}
inline bool Bfs(){
for(int i = 0; i <= node_num; ++i) dis[i] = -1; //这里要根据你所建的点数来确定
dis[S] = 0;
queue<int> q;
q.push(S);
while(!q.empty()){
int u = q.front(); q.pop();
for(int i = head[u]; i != -1; i = edge[i].next){
int v = edge[i].to;
if(dis[v] == -1 && edge[i].w){//没有标记深度并且有残量
dis[v] = dis[u] + 1;
q.push(v);
}
}
}
return dis[T] != -1;
}
inline ll dfs(int u, ll flow) {
if(u == T) return flow;
ll del = flow;
for(int i = cur[u]; i != -1; i = edge[i].next){
cur[u] = edge[i].next;//当前弧优化,下次直接从cur[u]开始增广,节省时间
int v = edge[i].to;
if (dis[v] == dis[u] + 1 && edge[i].w > 0){//深度+1且残量大于0
ll ans = dfs(v, min(del, edge[i].w));//木桶原理
edge[i].w -= ans;//正向弧减增广流量
edge[i ^ 1].w += ans;//反向弧加增广流量
del -= ans;//总流量减增广流量
if(del == 0) break;//总流量为0则不继续增广
}
}
return flow - del;//返回本次增广的流量
}
inline ll Dinic() {
ll ans = 0;
while(Bfs()) {
for(int i = 0; i <= node_num; ++i) cur[i] = head[i];
ans += dfs(S, inf);
}
return ans;
}
int dic[4][2] = {1,0, -1,0, 0,-1, 0,1};
/*
2
4
3
3 4
1 3
2 3
3
3
1 3
1 2
2 3
*/
int main() {
int Ti;
scanf("%d", &Ti);
while (Ti--) {
scanf("%d %d", &n, &m);
S = 0, T = 2*n+1;
node_num = T;
init();
int sum = 0;
for (int i = 1; i <= n; ++ i) Add_Edge(S, i, 1), Add_Edge(i+n, T, 1);
for (int i = 1, u, v; i <= m; ++ i) {
scanf("%d %d", &u, &v);
Add_Edge(u, v+n, 1);
}
cout << n - Dinic() << endl;
}
return 0;
}
View Code
The K-League
题意:网络流公平分配模型。告诉你有几支队伍,告诉你已知胜场,以及剩下需要进行的场数。问你哪些队伍可能获得胜利。
附上一个写的很好的博客:网络流和棒球赛淘汰问题 公平分配模板 足球联赛
(建模图)
AC代码:
#pragma GCC optimize(2)
#include <bits/stdc++.h>
using namespace std;
#define pii pair<long long, int>
typedef long long ll;
const int maxn = 1000 + 10;
const int maxm = 1e6 * 2 + 10;
const ll inf = 1e14;
const int mod = 1e9 + 7;
struct Edge{
int to;
int next;
ll w;
} edge[maxm];
int head[maxn], node_num;
int cur[maxn];//当前弧优化数组
int n, m, S, T, k;//点数,边数,源点,汇点
int dis[maxn];//Bfs深度
int cnt = 0;//边数
bool vis[maxn];
int w[maxn], d[maxn];
int a[maxn][maxn];
inline void init(){
cnt = 0;
for (int i = 0; i <= node_num; ++ i) head[i] = -1;
}
inline void Add_Edge(int u, int v, ll w){
edge[cnt].next = head[u];
edge[cnt].to = v;
edge[cnt].w = w;
head[u] = cnt++;
edge[cnt].next = head[v];
edge[cnt].to = u;
edge[cnt].w = 0;
head[v] = cnt++;
}
inline bool Bfs(){
for(int i = 0; i <= node_num; ++i) dis[i] = -1; //这里要根据你所建的点数来确定
dis[S] = 0;
queue<int> q;
q.push(S);
while(!q.empty()){
int u = q.front(); q.pop();
for(int i = head[u]; i != -1; i = edge[i].next){
int v = edge[i].to;
if(dis[v] == -1 && edge[i].w){//没有标记深度并且有残量
dis[v] = dis[u] + 1;
q.push(v);
}
}
}
return dis[T] != -1;
}
inline ll dfs(int u, ll flow){
if(u == T) return flow;
ll del = flow;
for(int i = cur[u]; i != -1; i = edge[i].next){
cur[u] = edge[i].next;//当前弧优化,下次直接从cur[u]开始增广,节省时间
int v = edge[i].to;
if (dis[v] == dis[u] + 1 && edge[i].w > 0){//深度+1且残量大于0
ll ans = dfs(v, min(del, edge[i].w));//木桶原理
edge[i].w -= ans;//正向弧减增广流量
edge[i ^ 1].w += ans;//反向弧加增广流量
del -= ans;//总流量减增广流量
if(del == 0) break;//总流量为0则不继续增广
}
}
return flow - del;//返回本次增广的流量
}
inline ll Dinic() {
ll ans = 0;
while(Bfs()) {
for(int i = 0; i <= node_num; ++i) cur[i] = head[i];
ans += dfs(S, inf);
}
return ans;
}
bool canWin(int team) {
//total代表的是team的最大胜场
int total = w[team];
for (int i = 0; i < n; ++ i)
total += a[team][i];
//如果存在其他队的胜场已经超过team的最大胜场,那么直接返回
for (int i = 0; i < n; ++ i)
if (w[i] > total) return 0;
S = 0, T = n*n+n+1;
node_num = T;
init();
int maxflow = 0;
for (int u = 0; u < n; ++ u) {
for (int v = u+1; v < n; ++ v) {
if (a[u][v] > 0) Add_Edge(S, u*n+v+1, a[u][v]);
maxflow += a[u][v];
Add_Edge(u*n+v+1, n*n+u+1, inf);
Add_Edge(u*n+v+1, n*n+v+1, inf);
}
if (w[u] < total) Add_Edge(n*n+u+1, T, total-w[u]);
}
return Dinic() == maxflow;
}
/*
3
3
2 0 1 1 0 2
0 2 2 2 0 2 2 2 0
3
4 0 2 2 0 4
0 1 1 1 0 1 1 1 0
4
0 3 3 1 1 3 3 0
0 0 0 2 0 0 1 0 0 1 0 0 2 0 0 0
*/
int main() {
int T;
scanf("%d", &T);
while(T--) {
scanf("%d", &n);
for(int i = 0; i < n; i ++)
scanf("%d%d", &w[i], &d[i]);
for(int i = 0; i < n; i ++)
for(int j = 0; j < n; j++)
scanf("%d", &a[i][j]);
bool first = true;
for(int i = 0; i < n; i++)
if(canWin(i)) {
if(first) first = false;
else printf(" ");
printf("%d", i+1);
}
printf("\n");
}
return 0;
}
View Code
Jamie's Contact Groups
这道题就是POJ的2289之前用二分图写过了。二分+网络流check就可以了。
我记得,这道题目的读取方式非常地让人难受。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#include<sstream>
using namespace std;
const int maxn=2010;
const int maxm=1e6+7;
const int inf=0x3f3f3f3f;
#define pb push_back
struct Node
{
int to;
int capa;
int next;
}edge[maxm];
int n,m;
int source,sink;
int cnt;
int head[maxn];
bool vis[maxn];
int dep[maxn];
char name[100010];
vector<int> vec[maxn];
void init()
{
memset(head,-1,sizeof(head));
cnt=0;
return;
}
void add(int u,int v,int capa)
{
edge[cnt].to=v;
edge[cnt].capa=capa;
edge[cnt].next=head[u];
head[u]=cnt++;
edge[cnt].to=u;
edge[cnt].capa=0;
edge[cnt].next=head[v];
head[v]=cnt++;
return;
}
bool bfs()
{
queue<int> que;
que.push(source);
memset(dep,-1,sizeof(dep));
dep[source]=0;
while(!que.empty())
{
int node=que.front();
que.pop();
for(int i=head[node];~i;i=edge[i].next)
{
int v=edge[i].to;
if(edge[i].capa>0&&dep[v]==-1)
{
dep[v]=dep[node]+1;
if(v==sink) return true;
que.push(v);
}
}
}
return dep[sink]!=-1;
}
int dfs(int node,int minn)
{
if(node==sink||minn==0)
{
return minn;
}
int r=0;
for(int i=head[node];~i;i=edge[i].next)
{
int v=edge[i].to;
if(dep[v]==dep[node]+1&&edge[i].capa>0)
{
int tmp=dfs(v,min(edge[i].capa,minn));
if(tmp>0)
{
edge[i].capa-=tmp;
edge[i^1].capa+=tmp;
r+=tmp;
minn-=tmp;
if(!minn) break;
}
}
}
if(!r) dep[node]=-1;
return r;
}
int dinic()
{
int maxflow=0;
while(bfs())
{
maxflow+=dfs(source,inf);
}
return maxflow;
}
void build(int mid)
{
for(int i=1;i<=n;i++)
{
add(source,i,1);
int len=vec[i].size();
for(int j=0;j<len;j++)
{
int x=vec[i][j];
add(i,n+x+1,1);
}
}
for(int i=1;i<=m;i++)
{
add(n+i,sink,mid);
}
return;
}
int main()
{
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
while(~scanf("%d%d",&n,&m))
{
getchar();
if(!n&&!m) break;
source=0;
sink=n+m+10;
string str;
int num;
for(int i=1;i<=n;i++)
{
vec[i].clear();
getline(cin,str);
stringstream stream(str);
stream>>name;
while(stream>>num)
{
vec[i].pb(num);
}
}
int left=0;
int right=n;
while(left<=right)
{
int mid=(left+right)/2;
init();
build(mid);
int ans=dinic();
if(ans==n)
{
right=mid-1;
}
else
{
left=mid+1;
}
}
printf("%d\n",left);
}
return 0;
}
View Code
Pool construction
这是一个最小割的很典型的题目。
每个洞要么是草地,要么是洞,我们假设草地是S集合,洞是Y集合,然后洞和草之间要建栅栏,也就可以理解为,用最少的花费将S集合和Y集合分开,这就是最小割的模型了。
初始时,从s点向所有的草地点连一条边,容量为d,割这些边意味着将这个草地变成洞。把每个洞的点向t连一条边,容量为f,割这些边意味着将这个洞变成草。
相邻的两个格子之间u和v,连两条边,u到v和 v到u,容量为b。割u到v这条边意味着u是草,v是洞,在之间建围栏。割v到u的点也类似。
题目还有一个要求,矩阵第一行/列和最后一行/列必须是草,所以,s向这些草连边容量为INF,代表这些边不能割。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <queue>
using namespace std;
const int maxn=3000+10;
const int maxw=55;
const int maxm=30000;
const int INF=2147000000;
struct Dinic{
int head[maxn],Next[maxm],to[maxm],cap[maxm],flow[maxm];
int sz,n,m,s,t;
bool vis[maxn];
int cur[maxn],d[maxn];
void init(int n){
this->n=n;
memset(head,-1,sizeof(head));
sz=-1;
}
void add_edge(int a,int b,int c){
++sz;
to[sz]=b;
cap[sz]=c;flow[sz]=0;
Next[sz]=head[a];head[a]=sz;
++sz;
to[sz]=a;
cap[sz]=c;flow[sz]=c;
Next[sz]=head[b];head[b]=sz;
}
bool BFS(){
memset(vis,0,sizeof(vis));
queue<int>Q;
vis[s]=1;
d[s]=0;
Q.push(s);
while(!Q.empty()){
int u=Q.front();Q.pop();
for(int i=head[u];i!=-1;i=Next[i]){
int v=to[i];
if(!vis[v]&&cap[i]>flow[i]){
vis[v]=1;
d[v]=d[u]+1;
Q.push(v);
}
}
}
return vis[t];
}
int DFS(int x,int a){
if(x==t||a==0)return a;
int Flow=0,f;
for(int& i=cur[x];i!=-1;i=Next[i]){
int v=to[i];
if(d[v]==d[x]+1&&(f=DFS(v,min(a,cap[i]-flow[i])))>0){
Flow+=f;
flow[i]+=f;
flow[i^1]-=f;
a-=f;
if(a==0)break;
}
}
return Flow;
}
int Maxflow(int s,int t){
this->s=s,this->t=t;
int Flow=0;
while(BFS()){
for(int i=0;i<=n;i++)
cur[i]=head[i];
Flow+=DFS(s,INF);
}
return Flow;
}
}dinic;
int w,h,d,f,b,T,ans;
char G[maxw][maxw];
int main(){
scanf("%d",&T);
for(int t=1;t<=T;t++){
ans=0;
scanf("%d%d",&w,&h);
scanf("%d%d%d",&d,&f,&b);
for(int i=1;i<=h;i++){
for(int j=1;j<=w;j++){
scanf(" %c",&G[i][j]);
if(i==1||j==1||i==h||j==w){
if(G[i][j]=='.'){
G[i][j]='#';
ans+=f;
}
}
}
}
dinic.init(w*h+2);
for(int i=1;i<=h;i++){
for(int j=1;j<=w;j++){
if(i==1||j==1||i==h||j==w){
dinic.add_edge(0,(i-1)*w+j,INF);
}else{
if(G[i][j]=='#'){
dinic.add_edge(0,(i-1)*w+j,d);
}
if(G[i][j]=='.'){
dinic.add_edge((i-1)*w+j,h*w+1,f);
}
}
if(i+1<=h){
dinic.add_edge((i-1)*w+j,i*w+j,b);
dinic.add_edge(i*w+j,(i-1)*w+j,b);
}
if(j+1<=w){
dinic.add_edge((i-1)*w+j,(i-1)*w+j+1,b);
dinic.add_edge((i-1)*w+j+1,(i-1)*w+j,b);
}
}
}
ans+=dinic.Maxflow(0,w*h+1);
printf("%d\n",ans);
}
return 0;
}
View Code
Cable TV Network
这题正好学了一下图论中点连通度和边连通度的概念。
1. 基本概念
(1)一个具有 N 个顶点的图,在去掉任意 K-1 个顶点后 (1<=K<=N) 所得的子图仍连通,则称 G 是连通的,
而去掉 K 个顶点后的图不连通, 那么K 称作图 G 的点连通度
(2)相应地如果至少去掉 K 条边使这个图不连通,则 K 成为图的边连通度
2. 求解思路
- 对于求解边联通度的问题,为每条边赋权值为1,然后求确定一点作为源点,枚举此点外的每个点作为汇点求最大流。
- 点联通度问题可以转换到边联通度问题上来,具体转换方法如下
- 若 G 为无向图,假设有n个点: (1) 原 G 图中的每个顶点 v 变成两个顶点 v' 和 v+n ,顶点 v 至 v+n 有一条弧(有向边)连接,弧容量为 1; (2) 原 G 图中的每条边 e = uv ,连一条 u+n 到 v 的弧,再连一条 v+n 到 u 的弧,容量均为INF (3) A” 为源顶点, B' 为汇顶点 注意:弧是有向边
- 若 G 为有向图,假设有n个点: (1) 原 G 图中的每个顶点 v 变成两个顶点 v' 和 v+n ,顶点 v 至 v+n 有一条弧(有向边)连接,弧容量为 1; (2) 原 G 图中的每条弧 e = uv 变成一条有向轨 u'u"v'v" ,其中轨上的弧 u"v' 的容量为 ∞; (3) A” 为源顶点, B' 为汇顶点
- 枚举源点 A" ,枚举汇点B',求 A" 到 B' 的最小最大流 F,注意源点汇点的拆点容量应该为inf(为不可割的点)
BTW, 这题网上有比较多的做法是只枚举汇点的,但是按理来说应该是源汇都要枚举。
还有些做法虽然枚举了源汇,但是确实以出点作为源点,入点作为汇点。这样是通过网络流特性来使得源汇不可割,但从理解上还是我这种建图方式较好理解。
#pragma GCC optimize(2)
#include <bits/stdc++.h>
using namespace std;
#define pii pair<long long, int>
typedef long long ll;
const int maxn = 1000 + 10;
const int maxm = 1e6 * 2 + 10;
const ll inf = 1e14;
const int mod = 1e9 + 7;
struct Edge{
int to;
int next;
ll w;
} edge[maxm];
int head[maxn], node_num;
int cur[maxn];//当前弧优化数组
int n, m, S, T, k;//点数,边数,源点,汇点
int dis[maxn];//Bfs深度
int cnt = 0;//边数
bool vis[maxn];
int w[maxn], d[maxn];
int a[maxn][maxn];
inline void init(){
cnt = 0;
for (int i = 0; i <= node_num; ++ i) head[i] = -1;
}
inline void Add_Edge(int u, int v, ll w){
edge[cnt].next = head[u];
edge[cnt].to = v;
edge[cnt].w = w;
head[u] = cnt++;
edge[cnt].next = head[v];
edge[cnt].to = u;
edge[cnt].w = 0;
head[v] = cnt++;
}
inline bool Bfs(){
for(int i = 0; i <= node_num; ++i) dis[i] = -1; //这里要根据你所建的点数来确定
dis[S] = 0;
queue<int> q;
q.push(S);
while(!q.empty()){
int u = q.front(); q.pop();
for(int i = head[u]; i != -1; i = edge[i].next){
int v = edge[i].to;
if(dis[v] == -1 && edge[i].w){//没有标记深度并且有残量
dis[v] = dis[u] + 1;
q.push(v);
}
}
}
return dis[T] != -1;
}
inline ll dfs(int u, ll flow){
if(u == T) return flow;
ll del = flow;
for(int i = cur[u]; i != -1; i = edge[i].next){
cur[u] = edge[i].next;//当前弧优化,下次直接从cur[u]开始增广,节省时间
int v = edge[i].to;
if (dis[v] == dis[u] + 1 && edge[i].w > 0){//深度+1且残量大于0
ll ans = dfs(v, min(del, edge[i].w));//木桶原理
edge[i].w -= ans;//正向弧减增广流量
edge[i ^ 1].w += ans;//反向弧加增广流量
del -= ans;//总流量减增广流量
if(del == 0) break;//总流量为0则不继续增广
}
}
return flow - del;//返回本次增广的流量
}
inline ll Dinic() {
ll ans = 0;
while(Bfs()) {
for(int i = 0; i <= node_num; ++i) cur[i] = head[i];
ans += dfs(S, inf);
}
return ans;
}
/*
0 0
1 0
3 3 (0,1) (0,2) (1,2)
2 0
5 7 (0,1) (0,2) (1,3) (1,2) (1,4) (2,3) (3,4)
*/
int main(){
while (~scanf("%d%d", &n, &m)){
int u[m+1], v[m+1];
for (int i = 0; i < m; i++)
scanf(" (%d,%d)", &u[i], &v[i]);//, u[i]++, v[i]++;
ll ans = inf;
node_num = 2*n;
for(int s=0;s<n;s++){
for(int t=s+1;t<n;t++){
init();
S = s, T = t;
for(int i=0;i<n;i++) {
if (i == s || i == t) Add_Edge(i,i+n,inf);
else Add_Edge(i,i+n,1);
}
for(int i=0;i<m;i++)
Add_Edge(u[i]+n,v[i],inf),Add_Edge(v[i]+n,u[i],inf);
ll temp = Dinic();
ans = min(ans,temp);
}
}
if(ans == inf){
if(n*(n-1)/2==m) printf("%d\n",n);
else printf("0\n");
}
else printf("%d\n",ans);
}
return 0;
}
View Code
Sabotage
求最小割边集。其实我觉得求出残余网络中容量为0的就好了,不就是最小割么。(不知道对不对)
(UPD20.9.11)跑满流的边只是可能为最小割边,反例: 1 ----(12)------2-------(12)--------3 假设S为1,T为3。如果满流的边是最小割的话,那么就是1-2和2-3,但是答案其实是1-2或者1-3。所以下述方法才是求最小割的正解!
但是网上的做法好像都是先最大流之后dfs染色,染色中遇到容量为0的跳过。
之后判断每条边左右节点的颜色是否不同就好了。
#pragma GCC optimize(2)
#include <bits/stdc++.h>
using namespace std;
#define pii pair<long long, int>
typedef long long ll;
const int maxn = 1000 + 10;
const int maxm = 1e6 * 2 + 10;
const ll inf = 1e14;
const int mod = 1e9 + 7;
struct Edge{
int to;
int next;
ll w;
} edge[maxm];
int head[maxn], node_num;
int cur[maxn];//当前弧优化数组
int n, m, S, T, k;//点数,边数,源点,汇点
int dis[maxn];//Bfs深度
int cnt = 0;//边数
bool vis[maxn];
inline void init(){
cnt = 0;
for (int i = 0; i <= node_num; ++ i) head[i] = -1;
memset(vis, 0, sizeof(vis));
}
inline void Add_Edge(int u, int v, ll w){
edge[cnt].next = head[u];
edge[cnt].to = v;
edge[cnt].w = w;
head[u] = cnt++;
edge[cnt].next = head[v];
edge[cnt].to = u;
edge[cnt].w = 0;
head[v] = cnt++;
}
inline bool Bfs(){
for(int i = 0; i <= node_num; ++i) dis[i] = -1; //这里要根据你所建的点数来确定
dis[S] = 0;
queue<int> q;
q.push(S);
while(!q.empty()){
int u = q.front(); q.pop();
for(int i = head[u]; i != -1; i = edge[i].next){
int v = edge[i].to;
if(dis[v] == -1 && edge[i].w){//没有标记深度并且有残量
dis[v] = dis[u] + 1;
q.push(v);
}
}
}
return dis[T] != -1;
}
inline ll dfs(int u, ll flow){
if(u == T) return flow;
ll del = flow;
for(int i = cur[u]; i != -1; i = edge[i].next){
cur[u] = edge[i].next;//当前弧优化,下次直接从cur[u]开始增广,节省时间
int v = edge[i].to;
if (dis[v] == dis[u] + 1 && edge[i].w > 0){//深度+1且残量大于0
ll ans = dfs(v, min(del, edge[i].w));//木桶原理
edge[i].w -= ans;//正向弧减增广流量
edge[i ^ 1].w += ans;//反向弧加增广流量
del -= ans;//总流量减增广流量
if(del == 0) break;//总流量为0则不继续增广
}
}
return flow - del;//返回本次增广的流量
}
inline ll Dinic() {
ll ans = 0;
while(Bfs()) {
for(int i = 0; i <= node_num; ++i) cur[i] = head[i];
ans += dfs(S, inf);
}
return ans;
}
void DFS(int u) {
vis[u] = 1;//为1表示是S集的点
for (int i = head[u]; ~i; i = edge[i].next) {
int v = edge[i].to;
if (vis[v] || !edge[i].w) continue;
DFS(v);
}
}
int main() {
while (~scanf("%d%d",&n,&m)) {
if (!n && !m) break;
node_num = n;
S = 1, T = 2;
init();
int x[m+1], y[m+1];
for (int i = 1; i <= m; ++ i) {
int u, v, w;
scanf("%d%d%d",&u,&v,&w);
Add_Edge(u, v, w), Add_Edge(v, u, w);
x[i] = u, y[i] = v;
}
Dinic();
DFS(S);
for(int i = 1; i <= m; i++) {
int u = x[i], v = y[i];
if (vis[u] && !vis[v] || vis[v] && !vis[u])
printf("%d %d\n", u, v);
}
cout << endl;
}
return 0;
}
View Code
Collectors Problem
这道题我好像见过。这也算是一类题了吧(分配问题)。
题目大意:Bob有一些贴纸,他可以和别人交换,他可以把自己独有的贴纸拿出去,也可以把重复的贴纸拿出去(有时候把独有的贴纸而不是重复的贴纸拿出去能换到更多贴纸)。
而且换的是自己没有的贴纸。
求Bob最后最多能有多少种贴纸。
解题思路:
题目意思很明确了。就算把重复的贴纸拿出去也不一定最优,贪心就不用尝试了。
全局资源调配得使用网络流模型。
建图方式:
①S点(看作是Bob)->所有物品:连一条边,cap是Bob持有贴纸数量。
②:所有朋友->所有物品:如果这个人持有的该贴纸数量>=2,连一条边,cap是贴纸数量-1。(原因是这些人只会把重复的贴纸拿出去)。
③:所有物品->所有朋友:如果这个人没有改物品,连一条边,cap=1,。(原因是这些人会接受自己没有的贴纸)
④:所有物品->T点:连一条边,cap=1,统计物品的种类。
这样建图之后,所有物品可以看作Bob的总资产,这个总资产可以流进,也可以流出,在这基础上做一次最大流,就是结果了。
#pragma GCC optimize(2)
#include <bits/stdc++.h>
using namespace std;
#define pii pair<long long, int>
typedef long long ll;
const int maxn = 1000 + 10;
const int maxm = 1e6 * 2 + 10;
const ll inf = 1e14;
const int mod = 1e9 + 7;
struct Edge{
int to;
int next;
ll w;
} edge[maxm];
int head[maxn], node_num;
int cur[maxn];//当前弧优化数组
int n, m, S, T, k;//点数,边数,源点,汇点
int dis[maxn];//Bfs深度
int tot = 0;//边数
bool vis[maxn];
inline void init(){
tot = 0;
for (int i = 0; i <= node_num; ++ i) head[i] = -1;
memset(vis, 0, sizeof(vis));
}
inline void Add_Edge(int u, int v, ll w){
edge[tot].next = head[u];
edge[tot].to = v;
edge[tot].w = w;
head[u] = tot++;
edge[tot].next = head[v];
edge[tot].to = u;
edge[tot].w = 0;
head[v] = tot++;
}
inline bool Bfs(){
for(int i = 0; i <= node_num; ++i) dis[i] = -1; //这里要根据你所建的点数来确定
dis[S] = 0;
queue<int> q;
q.push(S);
while(!q.empty()){
int u = q.front(); q.pop();
for(int i = head[u]; i != -1; i = edge[i].next){
int v = edge[i].to;
if(dis[v] == -1 && edge[i].w){//没有标记深度并且有残量
dis[v] = dis[u] + 1;
q.push(v);
}
}
}
return dis[T] != -1;
}
inline ll dfs(int u, ll flow){
if(u == T) return flow;
ll del = flow;
for(int i = cur[u]; i != -1; i = edge[i].next){
cur[u] = edge[i].next;//当前弧优化,下次直接从cur[u]开始增广,节省时间
int v = edge[i].to;
if (dis[v] == dis[u] + 1 && edge[i].w > 0){//深度+1且残量大于0
ll ans = dfs(v, min(del, edge[i].w));//木桶原理
edge[i].w -= ans;//正向弧减增广流量
edge[i ^ 1].w += ans;//反向弧加增广流量
del -= ans;//总流量减增广流量
if(del == 0) break;//总流量为0则不继续增广
}
}
return flow - del;//返回本次增广的流量
}
inline ll Dinic() {
ll ans = 0;
while(Bfs()) {
for(int i = 0; i <= node_num; ++i) cur[i] = head[i];
ans += dfs(S, inf);
}
return ans;
}
void DFS(int u) {
vis[u] = 1;//为1表示是S集的点
for (int i = head[u]; ~i; i = edge[i].next) {
int v = edge[i].to;
if (vis[v] || !edge[i].w) continue;
DFS(v);
}
}
/*
2
2 5
6 1 1 1 1 1 1
3 1 2 2
3 5
4 1 2 1 1
3 2 2 2
5 1 3 4 4 3
*/
int main() {
int Ti; scanf("%d", &Ti);
for (int Tii = 1; Tii <= Ti; ++ Tii) {
scanf("%d %d", &n, &m);
node_num = n+m+1;
S = 0, T = n+m+1;
init();
int cnt[n+1][m+1] = {0};
for (int u = 1, v; u <= n; u++) {
int num; scanf("%d", &num);
while (num--) {
scanf("%d", &v);
cnt[u][v]++;
}
}
//1号是自己
for (int i = 1; i <= m; i++)
Add_Edge(S, i, cnt[1][i]);
//2-n是其他人
for (int i = 2; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (!cnt[i][j]) Add_Edge(j, m+i, 1);
else Add_Edge(m+i, j, cnt[i][j]-1);
}
}
for (int i = 1; i <= m; i++)
Add_Edge(i, T, 1);
int ans = Dinic();
printf("Case #%d: %d\n", Tii, ans);
}
return 0;
}
View Code
Matrix Decompressing
思路:
这么也想不到用网络流解决,这个模型很不错。假设这个矩阵的每一行是水管,每一列是水管,每行有出水口流到每一列。
这样想比较好理解。然后每行的流量和每列的流量知道,就可以建图了。
建图过程,每行对应一个点,每列对应1个点,每行都可以流到每列,所以他们之间有边。
我们得假设他们是如何流向的,不如设从行流向列,那么添加源点,流向每行;添加汇点,被每列汇流。容量怎么设?我们要让每行都满流就行了,
那么行之和就是源点到该行的容量,同理汇点也如此。但是每行流向每列呢?注意每个格子的元素必须在1~20之间,所以把容量设为20,别让它流太多了。
注意到元素必须在1~20之间!!!那么这样增广路的话会出现有的完全0流怎么办?先将每个格子中的元素自减1,它的流下限总不会为负吧,计算完输出时再加回去不就行了。
#pragma GCC optimize(2)
#include <bits/stdc++.h>
using namespace std;
#define pii pair<long long, int>
typedef long long ll;
const int maxn = 1e6 + 10;
const int maxm = 1e5 * 2 + 10;
const ll inf = 1e18;
const int mod = 1e9 + 7;
struct Edge{
int to;
int next;
ll w;
} edge[maxn*2];
int head[maxn], node_num;
int cur[maxn];//当前弧优化数组
int n, m, S, T, k;//点数,边数,源点,汇点
int dis[maxn];//Bfs深度
int cnt = 0;//边数
bool vis[maxn];
inline void init(){
cnt = 0;
for (int i = 0; i <= node_num; ++ i) head[i] = -1;
}
inline void Add_Edge(int u, int v, ll w){
edge[cnt].next = head[u];
edge[cnt].to = v;
edge[cnt].w = w;
head[u] = cnt++;
edge[cnt].next = head[v];
edge[cnt].to = u;
edge[cnt].w = 0;
head[v] = cnt++;
}
inline bool Bfs(){
for(int i = 0; i <= node_num; ++i) dis[i] = -1; //这里要根据你所建的点数来确定
dis[S] = 0;
queue<int> q;
q.push(S);
while(!q.empty()){
int u = q.front(); q.pop();
for(int i = head[u]; ~i ; i = edge[i].next){
int v = edge[i].to;
if(dis[v] == -1 && edge[i].w){//没有标记深度并且有残量
dis[v] = dis[u] + 1;
q.push(v);
}
}
}
return dis[T] != -1;
}
inline ll dfs(int u, ll flow) {
if(u == T) return flow;
ll del = flow;
for(int i = cur[u]; ~i; i = edge[i].next){
cur[u] = edge[i].next;//当前弧优化,下次直接从cur[u]开始增广,节省时间
int v = edge[i].to;
if (dis[v] == dis[u] + 1 && edge[i].w > 0){//深度+1且残量大于0
ll ans = dfs(v, min(del, edge[i].w));//木桶原理
edge[i].w -= ans;//正向弧减增广流量
edge[i ^ 1].w += ans;//反向弧加增广流量
del -= ans;//总流量减增广流量
if(del == 0) break;//总流量为0则不继续增广
}
}
return flow - del;//返回本次增广的流量
}
inline ll Dinic() {
ll ans = 0;
while(Bfs()) {
for(int i = 0; i <= node_num; ++i) cur[i] = head[i];
ans += dfs(S, inf);
}
return ans;
}
int main()
{
//freopen("in.txt","r",stdin);
int Ti;
scanf("%d",&Ti);
for (int ti = 1; ti <= Ti; ++ ti) {
init();
cin>>n>>m;
S = 0, T = 1+n+m;
node_num = T;
int a[30]={0}, b[30]={0}, c[30]={0}, d[30]={0};
int rec[30][30] = {0};
for (int i=1; i<=n; i++) cin>>a[i];
for (int i=1; i<=m; i++) cin>>b[i];
for (int i=1; i<=n; i++) c[i] = a[i]-a[i-1]-m;
for (int i=1; i<=m; i++) d[i] = b[i]-b[i-1]-n;
for (int i=1; i<=n; i++)
Add_Edge(S,i,c[i]);
for (int i=1; i<=m; i++)
Add_Edge(n+i,T,d[i]);
for (int i=1; i<=n; i++)
for (int j=1; j<=m; j++)
Add_Edge(i,n+j,19), rec[i][j] = cnt-1;
Dinic();
cout << "Matrix " << ti << endl;
for (int i = 1; i <= n; ++ i) {
int j;
for (j = 1; j <= m-1; ++ j) {
cout << edge[rec[i][j]].w + 1 << " ";
}
cout << edge[rec[i][m]].w +1<< endl;//edge[j].w+1 << endl;
}
cout << endl;
}
return 0;
}
View Code
Frequency Hopping
这道题真的很好。为了做这道题我特地去重新学习了一遍网络流。
题意:问你是否在网络流中存在流量为C的流,如果不存在是否能修改一条边使得存在。
思路:存在流量为C那么只需要最大流>=C就好了,因为最大流可以不跑满。
所以possible的条件就是最大流.>= C
通过最大流最小割定理我们知道,当流量是最大流的时候,割边流量肯定跑满,所以修改容量的边肯定是割边。
所以我们枚举割边去修改
(暴力会超时。所以这里要提两点优化:
第一点是当跑最大流的时候一旦流量>=C那么直接跳出。
第二点是从跑了一遍最大流的残余网络上再跑最大流。
第一个点是比较好理解的就不讲了。第二点是因为Dinic算法是“阻塞流”,所以跑了一边dinic之后他会把一些道路给阻塞,就会使得增广的路减少从而达到优化时间的目的。
)
如果每一条割边都不能满足就是not possible
(最让我难受的是我的板子似乎不能满足这道题,改了一个上午还是超时。最后还是嫖了白书上的板子,以防日后会遇到相同的题目,用自己的板子写不出来。)
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
#define maxn 110
#define maxm 22010
typedef long long ll;
#define INF 0x3f3f3f3f
#define cl(x,v); memset(x,v,sizeof(x));
struct Edge{
int from,to,cap,flow;
};
bool cmp(const Edge& a,const Edge& b)
{
return a.from < b.from||(a.from==b.from&&a.to<b.to);
}
struct Dinic{
int n,m,s,t;
vector<Edge>edges;
vector<int>g[maxn];
bool vis[maxn];
int d[maxn];
int cur[maxm];
void init(int n)
{
this->n=n;
for(int i=0;i<=n;i++)g[i].clear();
edges.clear();
}
void AddEdge(int from,int to,int cap)
{
edges.push_back((Edge){from,to,cap,0});
edges.push_back((Edge){to,from,0,0});
m=edges.size();
g[from].push_back(m-2);
g[to].push_back(m-1);
}
bool BFS()
{
cl(vis,0);
queue<int>q;
q.push(s);
d[s]=0;
vis[s]=1;
while(!q.empty())
{
int x=q.front();
q.pop();
for(int i=0;i<g[x].size();i++)
{
Edge& e=edges[g[x][i]];
if(!vis[e.to]&&e.cap>e.flow)
{
vis[e.to]=1;
d[e.to]=d[x]+1;
q.push(e.to);
}
}
}
return vis[t];
}
int DFS(int x,int a)
{
if(x==t||a==0)return a;
int flow=0,f;
for(int& i=cur[x];i<g[x].size();i++)
{
Edge& e=edges[g[x][i]];
if(d[x]+1==d[e.to]&&(f=DFS(e.to,min(a,e.cap-e.flow)))>0)
{
e.flow+=f;
edges[g[x][i]^1].flow-=f;
flow+=f;
a-=f;
if(a==0)break;
}
}
return flow;
}
int Maxflow(int s,int t,int need)
{
this->s=s;
this->t=t;
int flow=0;
while(BFS())
{
cl(cur,0);
flow+=DFS(s,INF);
if(flow>need)return flow;
}
return flow;
}
vector<int> Mincut()
{
BFS();
vector<int>ans;
for(int i=0;i<edges.size();i++)
{
Edge& e=edges[i];
if(vis[e.from]&&!vis[e.to]&&e.cap>0)
ans.push_back(i);
}
return ans;
}
void Reduce()
{
for(int i=0;i<edges.size();i++)
edges[i].cap-=edges[i].flow;
}
void ClearFlow()
{
for(int i=0;i<edges.size();i++)
edges[i].flow=0;
}
};
Dinic solver;
int main()
{
int N,E,C,cas=0;
while(scanf("%d%d%d",&N,&E,&C)!=EOF)
{
if(N==0)break;
solver.init(N);
int a,b,c;
while(E--)
{
scanf("%d%d%d",&a,&b,&c);
solver.AddEdge(a,b,c);
}
int flow=solver.Maxflow(1,N,INF);
printf("Case %d: ",++cas);
if(flow>=C)printf("possible\n");
else
{
vector<int>cut=solver.Mincut();
solver.Reduce();
vector<Edge>ans;
for(int i=0;i<cut.size();i++)
{
Edge& e=solver.edges[cut[i]];
int temp=e.cap;
e.cap=C;
solver.ClearFlow();
if(flow+solver.Maxflow(1,N,C-flow)>=C)ans.push_back(e);
e.cap=temp;
}
if(ans.empty())printf("not possible\n");
else
{
sort(ans.begin(),ans.end(),cmp);
printf("possible option:(%d,%d)",ans[0].from,ans[0].to);
for(int i=1;i<ans.size();i++)
printf(",(%d,%d)",ans[i].from,ans[i].to);
printf("\n");
}
}
}
return 0;
}
View Code
SAM I AM
这道题跟上面那道挺像的。这道题让我解决了上面遗留的疑惑。同时也让我对最小割理解更为深刻。
#pragma GCC optimize(2)
#include <bits/stdc++.h>
using namespace std;
#define pii pair<long long, int>
typedef long long ll;
const int maxn = 3000 + 10;
const int maxm = 1e6 * 2 + 100;
const ll inf = 1e14;
const int mod = 1e9 + 7;
struct Edge{
int to;
int next;
ll w;
} edge[maxm];
int head[maxn], node_num;
int cur[maxn];//当前弧优化数组
int n, m, S, T, num;//点数,边数,源点,汇点
int dis[maxn];//Bfs深度
int cnt = 0;//边数
bool vis[maxn];
inline void init(){
cnt = 0;
for (int i = 0; i <= node_num; ++ i) head[i] = -1;
memset(vis, 0, sizeof(vis));
}
inline void Add_Edge(int u, int v, ll w){
edge[cnt].next = head[u];
edge[cnt].to = v;
edge[cnt].w = w;
head[u] = cnt++;
edge[cnt].next = head[v];
edge[cnt].to = u;
edge[cnt].w = 0;
head[v] = cnt++;
}
inline bool Bfs(){
for(int i = 0; i <= node_num; ++i) dis[i] = -1; //这里要根据你所建的点数来确定
dis[S] = 0;
queue<int> q;
q.push(S);
while(!q.empty()){
int u = q.front(); q.pop();
for(int i = head[u]; i != -1; i = edge[i].next){
int v = edge[i].to;
if(dis[v] == -1 && edge[i].w){//没有标记深度并且有残量
dis[v] = dis[u] + 1;
q.push(v);
}
}
}
return dis[T] != -1;
}
inline ll dfs(int u, ll flow){
if(u == T) return flow;
ll del = flow;
for(int i = cur[u]; i != -1; i = edge[i].next){
cur[u] = edge[i].next;//当前弧优化,下次直接从cur[u]开始增广,节省时间
int v = edge[i].to;
if (dis[v] == dis[u] + 1 && edge[i].w > 0){//深度+1且残量大于0
ll ans = dfs(v, min(del, edge[i].w));//木桶原理
edge[i].w -= ans;//正向弧减增广流量
edge[i ^ 1].w += ans;//反向弧加增广流量
del -= ans;//总流量减增广流量
if(del == 0) break;//总流量为0则不继续增广
}
}
return flow - del;//返回本次增广的流量
}
inline ll Dinic() {
ll ans = 0;
while(Bfs()) {
for(int i = 0; i <= node_num; ++i) cur[i] = head[i];
ans += dfs(S, inf);
}
return ans;
}
void DFS(int u) {
vis[u] = 1;//为1表示是S集的点
for (int i = head[u]; ~i; i = edge[i].next) {
int v = edge[i].to;
if (vis[v] || !edge[i].w) continue;
DFS(v);
}
}
int main() {
while (~scanf("%d%d%d",&n,&m,&num)) {
if (!n && !m && !num) break;
node_num = n+m+1;
S = 0, T = m+n+1;
init();
for (int i = 1; i <= n; ++ i) Add_Edge(S, i, 1);
for (int i = 1; i <= m; ++ i) Add_Edge(i+n, T, 1);
for (int i = 1,u,v; i <= num; ++ i)
scanf("%d%d",&u,&v), Add_Edge(u, v+n, 1);
printf("%lld ", Dinic());
DFS(S);
for (int i = 1; i <= n; ++ i)
if (!vis[i]) printf("r%d ", i);
for (int i = n+1; i <= m+n; ++ i)
if (vis[i]) printf("c%d ", i-n);
cout << endl;
}
return 0;
}
/*
4 4 3
1 1
1 4
3 2
4 4 2
1 1
2 2
0 0 0
*/
View Code
Power Transmission
这道题算是比较综合的题目吧,多源多汇+点容量限制。还行。
#pragma GCC optimize(2)
#include <bits/stdc++.h>
using namespace std;
#define pii pair<long long, int>
typedef long long ll;
const int maxn = 3000 + 10;
const int maxm = 1e6 * 2 + 100;
const ll inf = 1e14;
const int mod = 1e9 + 7;
struct Edge{
int to;
int next;
ll w;
} edge[maxm];
int head[maxn], node_num;
int cur[maxn];//当前弧优化数组
int n, m, S, T, num;//点数,边数,源点,汇点
int dis[maxn];//Bfs深度
int cnt = 0;//边数
bool vis[maxn];
inline void init(){
cnt = 0;
for (int i = 0; i <= node_num; ++ i) head[i] = -1;
memset(vis, 0, sizeof(vis));
}
inline void Add_Edge(int u, int v, ll w){
edge[cnt].next = head[u];
edge[cnt].to = v;
edge[cnt].w = w;
head[u] = cnt++;
edge[cnt].next = head[v];
edge[cnt].to = u;
edge[cnt].w = 0;
head[v] = cnt++;
}
inline bool Bfs(){
for(int i = 0; i <= node_num; ++i) dis[i] = -1; //这里要根据你所建的点数来确定
dis[S] = 0;
queue<int> q;
q.push(S);
while(!q.empty()){
int u = q.front(); q.pop();
for(int i = head[u]; i != -1; i = edge[i].next){
int v = edge[i].to;
if(dis[v] == -1 && edge[i].w){//没有标记深度并且有残量
dis[v] = dis[u] + 1;
q.push(v);
}
}
}
return dis[T] != -1;
}
inline ll dfs(int u, ll flow){
if(u == T) return flow;
ll del = flow;
for(int i = cur[u]; i != -1; i = edge[i].next){
cur[u] = edge[i].next;//当前弧优化,下次直接从cur[u]开始增广,节省时间
int v = edge[i].to;
if (dis[v] == dis[u] + 1 && edge[i].w > 0){//深度+1且残量大于0
ll ans = dfs(v, min(del, edge[i].w));//木桶原理
edge[i].w -= ans;//正向弧减增广流量
edge[i ^ 1].w += ans;//反向弧加增广流量
del -= ans;//总流量减增广流量
if(del == 0) break;//总流量为0则不继续增广
}
}
return flow - del;//返回本次增广的流量
}
inline ll Dinic() {
ll ans = 0;
while(Bfs()) {
for(int i = 0; i <= node_num; ++i) cur[i] = head[i];
ans += dfs(S, inf);
}
return ans;
}
void DFS(int u) {
vis[u] = 1;//为1表示是S集的点
for (int i = head[u]; ~i; i = edge[i].next) {
int v = edge[i].to;
if (vis[v] || !edge[i].w) continue;
DFS(v);
}
}
int main() {
//freopen("out.txt","w",stdout);
while (~scanf("%d",&n)) {
node_num = 2*n+1;
S = 0, T = 2*n+1;
init();
for (int i = 1, w; i <= n; ++ i)
scanf("%d", &w), Add_Edge(i, i+n, w);
scanf("%d", &m);
for (int i = 1,u,v,w; i <= m; ++ i)
scanf("%d%d%d",&u, &v, &w), Add_Edge(u+n, v, w);
int ed, st; scanf("%d%d", &st, &ed);
for (int i = 1, tt; i <= st; ++ i)
scanf("%d", &tt), Add_Edge(S, tt, inf);
for (int i = 1, tt; i <= ed; ++ i)
scanf("%d", &tt), Add_Edge(tt+n, T, inf);
printf("%lld\n", Dinic());
}
return 0;
}
/*
4 4 3
1 1
1 4
3 2
4 4 2
1 1
2 2
0 0 0
*/
View Code
Down Went The Titanic
多源多汇拆点。跟上一题差不多。只不过换成了矩阵的形式。
#pragma GCC optimize(2)
#include <bits/stdc++.h>
using namespace std;
#define pii pair<long long, int>
typedef long long ll;
const int maxn = 3000 + 10;
const int maxm = 1e6 * 2 + 100;
const ll inf = 1e14;
const int mod = 1e9 + 7;
struct Edge{
int to;
int next;
ll w;
} edge[maxm];
int head[maxn], node_num;
int cur[maxn];//当前弧优化数组
int n, m, S, T, c;//点数,边数,源点,汇点
int dis[maxn];//Bfs深度
int cnt = 0;//边数
bool vis[maxn];
char G[35][35];
inline void init(){
cnt = 0;
for (int i = 0; i <= node_num; ++ i) head[i] = -1;
memset(vis, 0, sizeof(vis));
}
inline void Add_Edge(int u, int v, ll w){
edge[cnt].next = head[u];
edge[cnt].to = v;
edge[cnt].w = w;
head[u] = cnt++;
edge[cnt].next = head[v];
edge[cnt].to = u;
edge[cnt].w = 0;
head[v] = cnt++;
}
inline bool Bfs(){
for(int i = 0; i <= node_num; ++i) dis[i] = -1; //这里要根据你所建的点数来确定
dis[S] = 0;
queue<int> q;
q.push(S);
while(!q.empty()){
int u = q.front(); q.pop();
for(int i = head[u]; i != -1; i = edge[i].next){
int v = edge[i].to;
if(dis[v] == -1 && edge[i].w){//没有标记深度并且有残量
dis[v] = dis[u] + 1;
q.push(v);
}
}
}
return dis[T] != -1;
}
inline ll dfs(int u, ll flow){
if(u == T) return flow;
ll del = flow;
for(int i = cur[u]; i != -1; i = edge[i].next){
cur[u] = edge[i].next;//当前弧优化,下次直接从cur[u]开始增广,节省时间
int v = edge[i].to;
if (dis[v] == dis[u] + 1 && edge[i].w > 0){//深度+1且残量大于0
ll ans = dfs(v, min(del, edge[i].w));//木桶原理
edge[i].w -= ans;//正向弧减增广流量
edge[i ^ 1].w += ans;//反向弧加增广流量
del -= ans;//总流量减增广流量
if(del == 0) break;//总流量为0则不继续增广
}
}
return flow - del;//返回本次增广的流量
}
inline ll Dinic() {
ll ans = 0;
while(Bfs()) {
for(int i = 0; i <= node_num; ++i) cur[i] = head[i];
ans += dfs(S, inf);
}
return ans;
}
void DFS(int u) {
vis[u] = 1;//为1表示是S集的点
for (int i = head[u]; ~i; i = edge[i].next) {
int v = edge[i].to;
if (vis[v] || !edge[i].w) continue;
DFS(v);
}
}
int dic[4][2] ={0,1,0,-1,1,0,-1,0};
int main() {
//freopen("out.txt","w",stdout);
while (~scanf("%d%d%d",&n,&m,&c)) {
node_num = 2*n*m+1;
S = 0, T = 2*n*m+1;
init();
for (int i = 1, w; i <= n; ++ i) scanf("%s", (G[i]+1));
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j <= m; ++ j) {
int id = (i-1)*m+j;
if (G[i][j] == '*') Add_Edge(S, id, 1), Add_Edge(id, id+n*m, 1);
else if (G[i][j] == '~') continue;
else if (G[i][j] == '.') Add_Edge(id, id+n*m, 1);
else if (G[i][j] == '@') Add_Edge(id, id+n*m, inf);
else if (G[i][j] == '#') Add_Edge(id+n*m, T, c), Add_Edge(id, id+n*m, inf);
for (int k = 0; k < 4; ++ k) {
int tx = i+dic[k][0];
int ty = j+dic[k][1];
int id1 = (tx-1)*m+ty;
if (tx < 1 || tx > n || ty < 1 || ty > m || G[tx][ty] == '*' || G[tx][ty] == '~') continue;
Add_Edge(id+n*m, id1, inf);
}
}
}
printf("%lld\n", Dinic());
}
return 0;
}
/*
4 4 3
1 1
1 4
3 2
4 4 2
1 1
2 2
0 0 0
*/
View Code
Objective: Berlin
这题在洛谷上居然是黑题。也不是很难的样子。
题意:给定一些航班,每个航班有人数,和起始终止时间,每次转机要花半小时,问限制时间内最多能有多少人从起始城市到终点城市
思路:dinic最大流,将每个班机作为一个点,并且拆点成i'和i'',从i'->i''的边的限制为班机的人数,两个班机i和j如果i的终点是j的起点,且时间间隔至少为30分钟则在i''->j'建立边,最后是源点和汇点的边。
#pragma GCC optimize(2)
#include <bits/stdc++.h>
using namespace std;
#define pii pair<long long, int>
typedef long long ll;
const int maxn = 1e4 + 10;
const int maxm = 1e6 * 2 + 100;
const ll inf = 1e14;
const int mod = 1e9 + 7;
struct Edge{
int to, next;
ll w;
} edge[maxm];
int head[maxn], node_num;
int cur[maxn];//当前弧优化数组
int n, m, S, T, c;//点数,边数,源点,汇点
int dis[maxn];//Bfs深度
int cnt = 0;//边数
//bool vis[maxn];
inline void init(){
cnt = 0;
for (int i = 0; i <= node_num; ++ i) head[i] = -1;
// memset(vis, 0, sizeof(vis));
}
inline void Add_Edge(int u, int v, ll w){
edge[cnt].next = head[u];
edge[cnt].to = v;
edge[cnt].w = w;
head[u] = cnt++;
edge[cnt].next = head[v];
edge[cnt].to = u;
edge[cnt].w = 0;
head[v] = cnt++;
}
inline bool Bfs(){
for(int i = 0; i <= node_num; ++i) dis[i] = -1; //这里要根据你所建的点数来确定
dis[S] = 0;
queue<int> q;
q.push(S);
while(!q.empty()){
int u = q.front(); q.pop();
for(int i = head[u]; i != -1; i = edge[i].next){
int v = edge[i].to;
if(dis[v] == -1 && edge[i].w){//没有标记深度并且有残量
dis[v] = dis[u] + 1;
q.push(v);
}
}
}
return dis[T] != -1;
}
inline ll dfs(int u, ll flow){
if(u == T) return flow;
ll del = flow;
for(int i = cur[u]; i != -1; i = edge[i].next){
cur[u] = edge[i].next;//当前弧优化,下次直接从cur[u]开始增广,节省时间
int v = edge[i].to;
if (dis[v] == dis[u] + 1 && edge[i].w > 0){//深度+1且残量大于0
ll ans = dfs(v, min(del, edge[i].w));//木桶原理
edge[i].w -= ans;//正向弧减增广流量
edge[i ^ 1].w += ans;//反向弧加增广流量
del -= ans;//总流量减增广流量
if(del == 0) break;//总流量为0则不继续增广
}
}
return flow - del;//返回本次增广的流量
}
inline ll Dinic() {
ll ans = 0;
while(Bfs()) {
for(int i = 0; i <= node_num; ++i) cur[i] = head[i];
ans += dfs(S, inf);
}
return ans;
}
struct Plane {
int u, v, num, stt, edt;
}P[maxn];
int main() {
//freopen("out.txt","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);
while (cin >> n) {
map<string, int> mp;
int hash_val = 0;
string st, ed; cin >> st >> ed;
mp[st] = hash_val++;
mp[ed] = hash_val++;
int end_time; cin >> end_time;
end_time = end_time/100*60 + end_time%100;
cin >> m;
S = 0, T = 2*m+1;
node_num = 2*m+1;
init();
for (int i = 1; i <= m; ++ i) {
string u, v;
int num, edt, stt;
cin >> u >> v >> num >> stt >> edt;
if (!mp[u]) mp[u] = hash_val++;
if (!mp[v]) mp[v] = hash_val++;
stt = stt/100*60 + stt%100;
edt = edt/100*60 + edt%100;
P[i] = {mp[u], mp[v], num, stt, edt};
}
for (int i = 1; i <= m; ++ i) {
Add_Edge(i, i+m, P[i].num);
if (P[i].u == mp[st]) Add_Edge(S, i, inf);
if (P[i].v == mp[ed] && P[i].edt <= end_time) Add_Edge(i+m, T, inf);
for (int j = 1; j <= m; ++j) {
if (i == j) continue;
if (P[i].v == P[j].u && P[j].stt - P[i].edt >= 30)
Add_Edge(i+m, j, inf);
}
}
printf("%lld\n", Dinic());
}
return 0;
}
/*
4
lisbon berlin
1500
9
lisbon london 6 1000 1100
london lisbon 6 1130 1230
lisbon paris 5 1000 1100
paris lisbon 4 1130 1230
london paris 1 1130 1300
london berlin 2 1340 1510
berlin london 2 1300 1430
paris berlin 10 1330 1500
berlin paris 9 1300 1430
*/
View Code
Hard Life
这道题是最大密度子图的模板题。
第一种方式
#pragma GCC optimize(2)
#include <bits/stdc++.h>
using namespace std;
#define pii pair<long long, int>
typedef long long ll;
const int maxn = 2011;
const int maxm = 1e6 * 2 + 100;
const double inf = 1e9;
const int mod = 1e9 + 7;
struct Edge{
int to, next;
double w;
} edge[maxn*4];
int head[maxn], node_num;
int cur[maxn];//当前弧优化数组
int n, m, S, T, c;//点数,边数,源点,汇点
int dis[maxn];//Bfs深度
int cnt = 0;//边数
int vis[maxn];
int tx[maxn], ty[maxn];
inline void Add_Edge(int u, int v, double w){
edge[cnt].next = head[u];
edge[cnt].to = v;
edge[cnt].w = w;
head[u] = cnt++;
edge[cnt].next = head[v];
edge[cnt].to = u;
edge[cnt].w = 0;
head[v] = cnt++;
}
inline bool Bfs(){
for(int i = 0; i <= node_num; ++i) dis[i] = -1;
dis[S] = 0;
queue<int> q;
q.push(S);
while(!q.empty()){
int u = q.front(); q.pop();
for(int i = head[u]; i != -1; i = edge[i].next){
int v = edge[i].to;
if(dis[v] == -1 && edge[i].w){//没有标记深度并且有残量
dis[v] = dis[u] + 1;
q.push(v);
}
}
}
return dis[T] != -1;
}
inline double dfs(int u, double flow){
if(u == T) return flow;
double del = flow;
for(int i = cur[u]; i != -1; i = edge[i].next){
cur[u] = edge[i].next;//当前弧优化,下次直接从cur[u]开始增广,节省时间
int v = edge[i].to;
if (dis[v] == dis[u] + 1 && edge[i].w > 0){//深度+1且残量大于0
double ans = dfs(v, min(del, edge[i].w));//木桶原理
edge[i].w -= ans;//正向弧减增广流量
edge[i ^ 1].w += ans;//反向弧加增广流量
del -= ans;//总流量减增广流量
if(del == 0) break;//总流量为0则不继续增广
}
}
return flow - del;//返回本次增广的流量
}
inline double Dinic() {
double ans = 0;
while(Bfs()) {
for(int i = 0; i <= node_num; ++i) cur[i] = head[i];
ans += dfs(S, inf);
}
return ans;
}
void build(double mid) {
cnt = 0;
memset(head, -1, sizeof(head));
for (int i = 1; i <= m; ++ i)
Add_Edge(n+i, tx[i], inf), Add_Edge(n+i, ty[i], inf);
for (int i = 1; i <= m; ++ i) Add_Edge(S, n+i, 1.0);
for (int i = 1; i <= n; ++ i) Add_Edge(i, T, mid);
}
void mark(int u) {
vis[u] = 1;
for (int i = head[u]; ~i; i = edge[i].next) {
int v = edge[i].to;
if (!edge[i].w || vis[v]) continue;
mark(v);
}
}
int main() {
//freopen("out.txt","w",stdout);
while (~scanf("%d%d", &n, &m)) {
if (m == 0) { printf("1\n1\n"); continue;}
for (int i = 1; i <= m; ++ i) scanf("%d%d", &tx[i], &ty[i]);
S = 0, T = n+m+1, node_num=T;
double L=0,R=m,M, eps=1.0/n/n;
while (R - L > eps) {
build(M = (L+R)/2);
if ((double)m-Dinic() > 1e-8) L = M;
else R = M;
}
//cout << L << endl;
build(L);
Dinic();
memset(vis, 0, sizeof(vis));
mark(S);
int ans=0;
for(int i=1;i<=n;i++) if(vis[i]) ans++;
printf("%d\n",ans);
for(int i=1;i<=n;i++) if(vis[i]) printf("%d\n",i);
}
return 0;
}
/*
5 6
1 5
5 4
4 2
2 5
1 2
3 1
*/
View Code
附第二种方法的代码。
View Code
餐巾计划问题
这个写得挺好的
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll, int> pii;
struct Edge {
int u, v;
ll flow, cap, cost;
int next;
};
const int MAXN = 5000, MAXM = 50000;
const ll INF = 0x3f3f3f3f3f3f3f3fLL;
int e_ptr = 1, S, T, head[MAXN+10]; Edge E[(MAXM+10)<<1];
ll dist[MAXN+10], MaxFlow, MinCost, delta;
int inq[MAXN+10], done[MAXN+10], vis[MAXN+10];
void AddEdge(int u, int v, ll cap, ll cost) {
E[++e_ptr] = (Edge) { u, v, 0, cap, cost, head[u] }; head[u] = e_ptr;
E[++e_ptr] = (Edge) { v, u, 0, 0, -cost, head[v] }; head[v] = e_ptr;
}
void Reduce() {
for(int i = 2; i <= e_ptr; i++)
E[i].cost += dist[E[i].v] - dist[E[i].u];
delta += dist[S];
}
bool BellmanFord() {
queue<int> Q;
memset(dist, 0x3f, sizeof(dist));
dist[T] = 0; Q.push(T); inq[T] = true;
while(!Q.empty()) {
int u = Q.front(); Q.pop(); inq[u] = false;
for(int j=head[u]; j; j=E[j].next) {
int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost;
if(f < c && dist[v] > dist[u] + len) {
dist[v] = dist[u] + len;
if(!inq[v]) {
inq[v] = true;
Q.push(v);
}
}
}
}
return dist[S] != INF;
}
bool Dijkstra() {
memset(dist, 0x3f, sizeof(dist));
memset(vis, 0, sizeof(vis));
priority_queue<pii,vector<pii>,greater<pii> > pq;
dist[T] = 0; pq.push({dist[T], T});
while(!pq.empty()) {
int u = pq.top().second; pq.pop();
if(vis[u]) continue;
vis[u] = 1;
for(int j=head[u]; j; j=E[j].next) {
int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost;
if(f < c && dist[v] > dist[u] + len) {
dist[v] = dist[u] + len;
pq.push({dist[v],v});
}
}
}
return dist[S] != INF;
}
ll DFS(int u, ll flow) {
if(u == T || flow == 0) return flow;
vis[u] = true; // differ from dinic
ll res = flow;
for(int j=head[u]; j; j=E[j].next) {
int v = E[j].v; ll f = E[j].flow, c = E[j].cap, len = E[j].cost;
if(!vis[v] && f < c && len == 0) { // not `dist[v] == dist[u]` ! they do not equal !
ll tmp = DFS(v, min(res, c-f)); // len = 0 <=> on the shortest path
E[j].flow += tmp;
E[j^1].flow -= tmp;
res -= tmp;
}
}
return flow - res;
}
void Augment() {
ll CurFlow = 0;
while(memset(vis, 0, sizeof(vis)),
(CurFlow = DFS(S, INF))) {
MaxFlow += CurFlow;
MinCost += CurFlow * delta;
}
}
void PrimalDual() {
if(!BellmanFord()) return;
Reduce(); Augment();
while(Dijkstra()) {
Reduce(); Augment();
}
}
int main() {
int N; scanf("%d", &N);
S = 0, T = 2*N+1;
for(int i = 1, x; i <= N; i++) {
scanf("%d", &x);
AddEdge(S, i, x, 0); //每天晚上收到x条脏毛巾
AddEdge(i+N, T, x, 0); //每天早上需要送出x条干净的毛巾
}
int p, m, f, s, n;
scanf("%d%d%d%d%d", &p, &m, &f, &n, &s);
for (int i = 1; i <= N; ++ i) {
if (i + 1 <= N) AddEdge(i, i+1, INF, 0); //每天晚上可以把剩下的毛巾留到明天晚上
if (i + m <= N) AddEdge(i, i+m+N, INF, f); //把晚上的毛巾送去洗到m天后的早上
if (i + n <= N) AddEdge(i, i+n+N, INF, s);//把晚上的毛巾送去洗到n天后的早上
AddEdge(S, i+n, INF, p); //每天早上可以买毛巾
}
PrimalDual();
printf("%lld", MinCost);
return 0;
}
View Code
方格取数加强版
这道题太裸了。唯一需要注意的点就是需要连一条用作经过的边。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll, int> pii;
struct Edge {
int u, v;
ll flow, cap, cost;
int next;
};
const int MAXN = 5000, MAXM = 50000;
const ll INF = 0x3f3f3f3f3f3f3f3fLL;
int e_ptr = 1, S, T, head[MAXN+10]; Edge E[(MAXM+10)<<1];
ll dist[MAXN+10], MaxFlow, MinCost, delta;
int inq[MAXN+10], done[MAXN+10], vis[MAXN+10];
void AddEdge(int u, int v, ll cap, ll cost) {
E[++e_ptr] = (Edge) { u, v, 0, cap, cost, head[u] }; head[u] = e_ptr;
E[++e_ptr] = (Edge) { v, u, 0, 0, -cost, head[v] }; head[v] = e_ptr;
}
void Reduce() {
for(int i = 2; i <= e_ptr; i++)
E[i].cost += dist[E[i].v] - dist[E[i].u];
delta += dist[S];
}
bool BellmanFord() {
queue<int> Q;
memset(dist, 0x3f, sizeof(dist));
dist[T] = 0; Q.push(T); inq[T] = true;
while(!Q.empty()) {
int u = Q.front(); Q.pop(); inq[u] = false;
for(int j=head[u]; j; j=E[j].next) {
int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost;
if(f < c && dist[v] > dist[u] + len) {
dist[v] = dist[u] + len;
if(!inq[v]) {
inq[v] = true;
Q.push(v);
}
}
}
}
return dist[S] != INF;
}
bool Dijkstra() {
memset(dist, 0x3f, sizeof(dist));
memset(vis, 0, sizeof(vis));
priority_queue<pii,vector<pii>,greater<pii> > pq;
dist[T] = 0; pq.push({dist[T], T});
while(!pq.empty()) {
int u = pq.top().second; pq.pop();
if(vis[u]) continue;
vis[u] = 1;
for(int j=head[u]; j; j=E[j].next) {
int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost;
if(f < c && dist[v] > dist[u] + len) {
dist[v] = dist[u] + len;
pq.push({dist[v],v});
}
}
}
return dist[S] != INF;
}
ll DFS(int u, ll flow) {
if(u == T || flow == 0) return flow;
vis[u] = true; // differ from dinic
ll res = flow;
for(int j=head[u]; j; j=E[j].next) {
int v = E[j].v; ll f = E[j].flow, c = E[j].cap, len = E[j].cost;
if(!vis[v] && f < c && len == 0) { // not `dist[v] == dist[u]` ! they do not equal !
ll tmp = DFS(v, min(res, c-f)); // len = 0 <=> on the shortest path
E[j].flow += tmp;
E[j^1].flow -= tmp;
res -= tmp;
}
}
return flow - res;
}
void Augment() {
ll CurFlow = 0;
while(memset(vis, 0, sizeof(vis)),
(CurFlow = DFS(S, INF))) {
MaxFlow += CurFlow;
MinCost += CurFlow * delta;
}
}
void PrimalDual() {
if(!BellmanFord()) return;
Reduce(); Augment();
while(Dijkstra()) {
Reduce(); Augment();
}
}
int g[55][55];
int dic[2][2] = {1,0,0,1};
int main() {
int n, k; scanf("%d%d", &n, &k);
S = 0, T = 2*n*n+1;
for(int i = 1; i <= n; i++)
for (int j = 1; j <= n; ++ j)
scanf("%d", &g[i][j]);
AddEdge(S, 1, k, 0), AddEdge(n*n*2, T, k, 0);
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
int id = (i-1)*n+j;
AddEdge(id, id+n*n, 1, -g[i][j]), AddEdge(id, id+n*n, INF, 0);
for (int k = 0; k < 2; ++ k) {
int tx = i+dic[k][0], ty = j+dic[k][1];
if (tx > n || ty > n) continue;
int _id = (tx-1)*n+ty;
AddEdge(id+n*n, _id, INF, 0);
}
}
}
PrimalDual();
printf("%lld", -MinCost);
return 0;
}
View Code
[SCOI2007]修车
这道题的建图十分的难想。大家都知道要拆点,但是怎么拆就是个问题。这个算是涨个知识,知道建图还能这么建。
之后只需要车向时间段连固定代价即可。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll, int> pii;
struct Edge {
int u, v;
ll flow, cap, cost;
int next;
};
const int MAXN = 5000, MAXM = 50000;
const ll INF = 0x3f3f3f3f3f3f3f3fLL;
int e_ptr = 1, S, T, head[MAXN+10]; Edge E[(MAXM+10)<<1];
ll dist[MAXN+10], MaxFlow, MinCost, delta;
int inq[MAXN+10], done[MAXN+10], vis[MAXN+10];
void AddEdge(int u, int v, ll cap, ll cost) {
E[++e_ptr] = (Edge) { u, v, 0, cap, cost, head[u] }; head[u] = e_ptr;
E[++e_ptr] = (Edge) { v, u, 0, 0, -cost, head[v] }; head[v] = e_ptr;
}
void Reduce() {
for(int i = 2; i <= e_ptr; i++)
E[i].cost += dist[E[i].v] - dist[E[i].u];
delta += dist[S];
}
bool BellmanFord() {
queue<int> Q;
memset(dist, 0x3f, sizeof(dist));
dist[T] = 0; Q.push(T); inq[T] = true;
while(!Q.empty()) {
int u = Q.front(); Q.pop(); inq[u] = false;
for(int j=head[u]; j; j=E[j].next) {
int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost;
if(f < c && dist[v] > dist[u] + len) {
dist[v] = dist[u] + len;
if(!inq[v]) {
inq[v] = true;
Q.push(v);
}
}
}
}
return dist[S] != INF;
}
bool Dijkstra() {
memset(dist, 0x3f, sizeof(dist));
memset(vis, 0, sizeof(vis));
priority_queue<pii,vector<pii>,greater<pii> > pq;
dist[T] = 0; pq.push({dist[T], T});
while(!pq.empty()) {
int u = pq.top().second; pq.pop();
if(vis[u]) continue;
vis[u] = 1;
for(int j=head[u]; j; j=E[j].next) {
int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost;
if(f < c && dist[v] > dist[u] + len) {
dist[v] = dist[u] + len;
pq.push({dist[v],v});
}
}
}
return dist[S] != INF;
}
ll DFS(int u, ll flow) {
if(u == T || flow == 0) return flow;
vis[u] = true; // differ from dinic
ll res = flow;
for(int j=head[u]; j; j=E[j].next) {
int v = E[j].v; ll f = E[j].flow, c = E[j].cap, len = E[j].cost;
if(!vis[v] && f < c && len == 0) { // not `dist[v] == dist[u]` ! they do not equal !
ll tmp = DFS(v, min(res, c-f)); // len = 0 <=> on the shortest path
E[j].flow += tmp;
E[j^1].flow -= tmp;
res -= tmp;
}
}
return flow - res;
}
void Augment() {
ll CurFlow = 0;
while(memset(vis, 0, sizeof(vis)),
(CurFlow = DFS(S, INF))) {
MaxFlow += CurFlow;
MinCost += CurFlow * delta;
}
}
void PrimalDual() {
if(!BellmanFord()) return;
Reduce(); Augment();
while(Dijkstra()) {
Reduce(); Augment();
}
}
int cost[65][10];
int main() {
int n, m; scanf("%d%d", &m, &n); //n车,m人
S = 0, T = m*n+n+1;
for (int i = 1; i <= m; ++ i)
for (int j = 1; j <= n; ++ j)
AddEdge((i-1)*n+j+n, T, 1, 0);
for (int i = 1; i <= n; ++ i) { //最外层是车
AddEdge(S, i, 1, 0);
for (int j = 1; j <= m; ++ j) { //这一层是人
int temp; scanf("%d", &temp);
for (int k = 1; k <= n; ++ k) //这一层是枚举时间段
AddEdge(i, n+(j-1)*n+k, 1, k*temp);
}
}
PrimalDual();
printf("%.2f\n", 1.0*MinCost/n);
return 0;
}
View Code
[NOI2012]美食节
修车的加强版。区别就是每款车都有个数了,所以时间段建点会很多,会导致爆时间。
具体的优化就是需要根据每次哪个人倒数第几次修车被跑满了,就动态加入新的时间段。
附上嫖自洛谷的代码。
#include <iostream>
#include <iomanip>
#include <cstring>
#include <map>
#include <queue>
#define inf 2147483646
#define N 10000
using namespace std;
struct ed{
int u,w,next,f;
}e[1000000];
int g[1000][2000],a[2000];
int n,m,st=1,ans,cost,sm,fir[30000],c[30000],d[30000];
int vis[30000],sp[30000];
queue<int> q; bool v[30000];
map<int,int> ha;
void add(int x,int y,int w,int f)
{
e[++st].u=y; e[st].next=fir[x]; e[fir[x]=st].w=w; e[st].f=f;
e[++st].u=x; e[st].next=fir[y]; e[fir[y]=st].w=0; e[st].f=-f;
}
bool spfa()
{
for (int i=0;i<=N;i++) d[i]=inf/2,c[i]=fir[i],v[i]=0;
q.push(0); v[0]=1; d[0]=0;
while (!q.empty())
{
int k=q.front(); q.pop(); v[k]=0;
for (int i=fir[k];i;i=e[i].next){
int u=e[i].u,w=e[i].f;
if (d[u]>d[k]+w&&e[i].w){
d[u]=d[k]+w; if (!v[u]) v[u]=1,q.push(u);
}
}
}
return (d[N]<inf/2);
}
int dfs(int p,int now)
{
if (p==N){v[N]=1; return now;}
int mw=0; v[p]=1;
for (int i=fir[p];i;i=e[i].next)
{
int u=e[i].u,w=e[i].f;
if (d[u]==d[p]+w&&e[i].w&&(!v[u]||u==N))
if (mw=dfs(u,min(e[i].w,now)))
{
e[i].w-=mw; e[i^1].w+=mw;
cost+=mw*w; return mw;
}
}
}
void dinic()
{
while (spfa()) {
ans+=dfs(0,inf);
for (int i=fir[N];i;i=e[i].next){
int u=e[i].u,w=e[i].w;
if (w&&!vis[u]) {
vis[u]=1; int co=ha[u]; sp[co]++;
add(++sm,N,1,0); ha[sm]=co;
for (int i=1;i<=n;i++) add(i,sm,1,sp[co]*g[i][co]);
}
}
}
}
int main()
{
cin>>n>>m; int sum=0;
for (int i=1;i<=n;i++) cin>>a[i],sum+=a[i];
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++) cin>>g[i][j];
for (int i=1;i<=n;i++) add(0,i,a[i],0);
sm=n;
//for (int k=1;k<=n;k++) 时间K(总数不为n了)
for (int j=1;j<=m;j++) {//厨师j
add(++sm,N,1,0); ha[sm]=j; sp[j]=1;
for (int i=1;i<=n;i++) add(i,sm,1,g[i][j]); //菜i
}
dinic();
cout<<cost<<endl;
}
View Code
[SDOI2009]晨跑
路线不会相交,所以每个点只能走一遍。直接拆点,出点和入点连费用为0容量为1的边。
对于每条有向边,起点出点连终点入点,容量为1,费用为输入费用。
关于代码中S = 1+n(1号点的出点),T = n(n号点的入点),是因为S和T是可以重复经过的。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll, int> pii;
struct Edge {
int u, v;
ll flow, cap, cost;
int next;
};
const int MAXN = 5000, MAXM = 50000;
const ll LLINF = 0x3f3f3f3f3f3f3f3fLL;
int n, m;
int e_ptr = 1, S, T, head[MAXN+10]; Edge E[(MAXM+10)<<1];
ll dist[MAXN+10], MaxFlow, MinCost, delta;
int inq[MAXN+10], done[MAXN+10], vis[MAXN+10];
void AddEdge(int u, int v, ll cap, ll cost) {
E[++e_ptr] = (Edge) { u, v, 0, cap, cost, head[u] }; head[u] = e_ptr;
E[++e_ptr] = (Edge) { v, u, 0, 0, -cost, head[v] }; head[v] = e_ptr;
}
void Reduce() {
for(int i = 2; i <= e_ptr; i++)
E[i].cost += dist[E[i].v] - dist[E[i].u];
delta += dist[S];
}
bool BellmanFord() {
queue<int> Q;
memset(dist, 0x3f, sizeof(dist));
dist[T] = 0; Q.push(T); inq[T] = true;
while(!Q.empty()) {
int u = Q.front(); Q.pop(); inq[u] = false;
for(int j=head[u]; j; j=E[j].next) {
int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost;
if(f < c && dist[v] > dist[u] + len) {
dist[v] = dist[u] + len;
if(!inq[v]) {
inq[v] = true;
Q.push(v);
}
}
}
}
return dist[S] != LLINF;
}
bool Dijkstra() {
memset(dist, 0x3f, sizeof(dist));
memset(vis, 0, sizeof(vis));
priority_queue<pii,vector<pii>,greater<pii> > pq;
dist[T] = 0; pq.push({dist[T], T});
while(!pq.empty()) {
int u = pq.top().second; pq.pop();
if(vis[u]) continue;
vis[u] = 1;
for(int j=head[u]; j; j=E[j].next) {
int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost;
if(f < c && dist[v] > dist[u] + len) {
dist[v] = dist[u] + len;
pq.push({dist[v],v});
}
}
}
return dist[S] != LLINF;
}
ll DFS(int u, ll flow) {
if(u == T || flow == 0) return flow;
vis[u] = true; // differ from dinic
ll res = flow;
for(int j=head[u]; j; j=E[j].next) {
int v = E[j].v; ll f = E[j].flow, c = E[j].cap, len = E[j].cost;
if(!vis[v] && f < c && len == 0) { // not `dist[v] == dist[u]` ! they do not equal !
ll tmp = DFS(v, min(res, c-f)); // len = 0 <=> on the shortest path
E[j].flow += tmp;
E[j^1].flow -= tmp;
res -= tmp;
}
}
return flow - res;
}
void Augment() {
ll CurFlow = 0;
while(memset(vis, 0, sizeof(vis)),
(CurFlow = DFS(S, LLINF))) {
MaxFlow += CurFlow;
MinCost += CurFlow * delta;
}
}
void PrimalDual() {
if(!BellmanFord()) return;
Reduce(); Augment();
while(Dijkstra()) {
Reduce(); Augment();
}
}
int main() {
scanf("%d%d", &n, &m);
S = n+1, T = n;
for (int i = 1; i <= n; ++ i)
AddEdge(i, i+n, 1, 0);
for(int i=1; i<=m; i++) {
int u, v, cost;
scanf("%d%d%d", &u, &v, &cost);
AddEdge(u+n, v, 1, cost);
}
PrimalDual();
printf("%lld %lld", MaxFlow, MinCost);
return 0;
}
View Code
[SDOI2010]星际竞速
第一道最小路径覆盖的费用流的题目。
再费用流中,最小路径覆盖就不能像在最大流中一样了。
但是背后的思想都是一样的。
在最大流中的最小路径覆盖的方法是:先将个点拆点为
和
。然后由源点向所有的
点连一条容量为
的边,再由所有的
点向汇点连一条容量为
的边,对于每条边
,由
向
连一条容量为
的边,跑一遍最大流后,
减去最大流就是最小路径覆盖。
为什么可以这样做呢?可以发现,上面其实是一个二分图,最大流实际上就是最大匹配数。在这里可以发现,匹配中每连一条边,路径的数量就减
算是给自己长个见识把。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll, int> pii;
struct Edge {
int u, v;
ll flow, cap, cost;
int next;
};
const int MAXN = 5000, MAXM = 50000;
const ll LLINF = 0x3f3f3f3f3f3f3f3fLL;
int e_ptr = 1, S, T, n, m, head[MAXN+10]; Edge E[(MAXM+10)<<1];
ll dist[MAXN+10], MaxFlow, MinCost, delta;
int inq[MAXN+10], done[MAXN+10], vis[MAXN+10];
void AddEdge(int u, int v, ll cap, ll cost) {
E[++e_ptr] = (Edge) { u, v, 0, cap, cost, head[u] }; head[u] = e_ptr;
E[++e_ptr] = (Edge) { v, u, 0, 0, -cost, head[v] }; head[v] = e_ptr;
}
void Reduce() {
for(int i = 2; i <= e_ptr; i++)
E[i].cost += dist[E[i].v] - dist[E[i].u];
delta += dist[S];
}
bool BellmanFord() {
queue<int> Q;
memset(dist, 0x3f, sizeof(dist));
dist[T] = 0; Q.push(T); inq[T] = true;
while(!Q.empty()) {
int u = Q.front(); Q.pop(); inq[u] = false;
for(int j=head[u]; j; j=E[j].next) {
int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost;
if(f < c && dist[v] > dist[u] + len) {
dist[v] = dist[u] + len;
if(!inq[v]) {
inq[v] = true;
Q.push(v);
}
}
}
}
return dist[S] != LLINF;
}
bool Dijkstra() {
memset(dist, 0x3f, sizeof(dist));
memset(vis, 0, sizeof(vis));
priority_queue<pii,vector<pii>,greater<pii> > pq;
dist[T] = 0; pq.push({dist[T], T});
while(!pq.empty()) {
int u = pq.top().second; pq.pop();
if(vis[u]) continue;
vis[u] = 1;
for(int j=head[u]; j; j=E[j].next) {
int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost;
if(f < c && dist[v] > dist[u] + len) {
dist[v] = dist[u] + len;
pq.push({dist[v],v});
}
}
}
return dist[S] != LLINF;
}
ll DFS(int u, ll flow) {
if(u == T || flow == 0) return flow;
vis[u] = true; // differ from dinic
ll res = flow;
for(int j=head[u]; j; j=E[j].next) {
int v = E[j].v; ll f = E[j].flow, c = E[j].cap, len = E[j].cost;
if(!vis[v] && f < c && len == 0) { // not `dist[v] == dist[u]` ! they do not equal !
ll tmp = DFS(v, min(res, c-f)); // len = 0 <=> on the shortest path
E[j].flow += tmp;
E[j^1].flow -= tmp;
res -= tmp;
}
}
return flow - res;
}
void Augment() {
ll CurFlow = 0;
while(memset(vis, 0, sizeof(vis)),
(CurFlow = DFS(S, LLINF))) {
MaxFlow += CurFlow;
MinCost += CurFlow * delta;
}
}
void PrimalDual() {
if(!BellmanFord()) return;
Reduce(); Augment();
while(Dijkstra()) {
Reduce(); Augment();
}
}
int main() {
int u, v, cap, cost;
scanf("%d%d", &n, &m);
S = 0, T = n+n+1;
for (int i = 1; i <= n; i ++) {
int temp; scanf("%d", &temp);
AddEdge(S, i+n, 1, temp);
AddEdge(S, i, 1, 0);
AddEdge(i+n, T, 1, 0);
}
for (int i = 1, u, v, c; i <= m; i ++) {
scanf("%d%d%d", &u, &v, &c);
if (u > v) swap(u, v);
AddEdge(u, v+n, 1, c);
}
PrimalDual();
printf("%lld\n", MinCost);
return 0;
}
View Code
[HAOI2010]订货
比较裸。但是我最近拆点做多了,啥都拆点。不过这道题倒是不用。直接连就好了
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll, int> pii;
struct Edge {
int u, v;
ll flow, cap, cost;
int next;
};
const int MAXN = 5000, MAXM = 50000;
const ll LLINF = 0x3f3f3f3f3f3f3f3fLL;
int n, m;
int e_ptr = 1, S, T, head[MAXN+10]; Edge E[(MAXM+10)<<1];
ll dist[MAXN+10], MaxFlow, MinCost, delta;
int inq[MAXN+10], done[MAXN+10], vis[MAXN+10];
void AddEdge(int u, int v, ll cap, ll cost) {
E[++e_ptr] = (Edge) { u, v, 0, cap, cost, head[u] }; head[u] = e_ptr;
E[++e_ptr] = (Edge) { v, u, 0, 0, -cost, head[v] }; head[v] = e_ptr;
}
void Reduce() {
for(int i = 2; i <= e_ptr; i++)
E[i].cost += dist[E[i].v] - dist[E[i].u];
delta += dist[S];
}
bool BellmanFord() {
queue<int> Q;
memset(dist, 0x3f, sizeof(dist));
dist[T] = 0; Q.push(T); inq[T] = true;
while(!Q.empty()) {
int u = Q.front(); Q.pop(); inq[u] = false;
for(int j=head[u]; j; j=E[j].next) {
int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost;
if(f < c && dist[v] > dist[u] + len) {
dist[v] = dist[u] + len;
if(!inq[v]) {
inq[v] = true;
Q.push(v);
}
}
}
}
return dist[S] != LLINF;
}
bool Dijkstra() {
memset(dist, 0x3f, sizeof(dist));
memset(vis, 0, sizeof(vis));
priority_queue<pii,vector<pii>,greater<pii> > pq;
dist[T] = 0; pq.push({dist[T], T});
while(!pq.empty()) {
int u = pq.top().second; pq.pop();
if(vis[u]) continue;
vis[u] = 1;
for(int j=head[u]; j; j=E[j].next) {
int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost;
if(f < c && dist[v] > dist[u] + len) {
dist[v] = dist[u] + len;
pq.push({dist[v],v});
}
}
}
return dist[S] != LLINF;
}
ll DFS(int u, ll flow) {
if(u == T || flow == 0) return flow;
vis[u] = true; // differ from dinic
ll res = flow;
for(int j=head[u]; j; j=E[j].next) {
int v = E[j].v; ll f = E[j].flow, c = E[j].cap, len = E[j].cost;
if(!vis[v] && f < c && len == 0) { // not `dist[v] == dist[u]` ! they do not equal !
ll tmp = DFS(v, min(res, c-f)); // len = 0 <=> on the shortest path
E[j].flow += tmp;
E[j^1].flow -= tmp;
res -= tmp;
}
}
return flow - res;
}
void Augment() {
ll CurFlow = 0;
while(memset(vis, 0, sizeof(vis)),
(CurFlow = DFS(S, LLINF))) {
MaxFlow += CurFlow;
MinCost += CurFlow * delta;
}
}
void PrimalDual() {
if(!BellmanFord()) return;
Reduce(); Augment();
while(Dijkstra()) {
Reduce(); Augment();
}
}
int main() {
int store;
scanf("%d%d%d", &n, &m, &store);
S = 0, T = 2*n+1;
for (int i = 1; i <= n; ++ i) {
int temp; scanf("%d", &temp);
AddEdge(i, T, temp, 0);
// AddEdge(i, i+n, store, 0);
if (i < n) AddEdge(i, i+1, store, m);
}
for (int i = 1; i <= n; ++ i) {
int temp; scanf("%d", &temp);
AddEdge(S, i, LLINF, temp);
}
PrimalDual();
printf("%lld\n", MinCost);
return 0;
}
View Code
[ZJOI2010]网络扩容
这道题我以为会是像上面那道UVA上的网络扩容一样的,之后就在求最小割,之后枚举边,之后扩容跑MFMC。
但是其实这道题可以很简单就可以,原边的费用都是0,之后跑一次最大流。
清除原来的图,之后在建一次图,在原图基础上再加一条容量无限费用为wi的。同时新开一个T = n+1,n连向T容量原先的最大流+k费用为0.
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll, int> pii;
struct Edge {
int u, v;
ll flow, cap, cost;
int next;
};
const int MAXN = 5000+10, MAXM = 50000;
const ll LLINF = 0x3f3f3f3f3f3f3f3fLL;
int n, m, k;
int e_ptr = 1, S, T, head[MAXN]; Edge E[(MAXM)<<1];
ll dist[MAXN], MaxFlow, MinCost, delta;
int inq[MAXN], done[MAXN], vis[MAXN];
int color[MAXN];
void AddEdge(int u, int v, ll cap, ll cost) {
E[++e_ptr] = (Edge) { u, v, 0, cap, cost, head[u] }; head[u] = e_ptr;
E[++e_ptr] = (Edge) { v, u, 0, 0, -cost, head[v] }; head[v] = e_ptr;
}
void Reduce() {
for(int i = 2; i <= e_ptr; i++)
E[i].cost += dist[E[i].v] - dist[E[i].u];
delta += dist[S];
}
bool BellmanFord() {
queue<int> Q;
memset(dist, 0x3f, sizeof(dist));
dist[T] = 0; Q.push(T); inq[T] = true;
while(!Q.empty()) {
int u = Q.front(); Q.pop(); inq[u] = false;
for(int j=head[u]; j; j=E[j].next) {
int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost;
if(f < c && dist[v] > dist[u] + len) {
dist[v] = dist[u] + len;
if(!inq[v]) {
inq[v] = true;
Q.push(v);
}
}
}
}
return dist[S] != LLINF;
}
bool Dijkstra() {
memset(dist, 0x3f, sizeof(dist));
memset(vis, 0, sizeof(vis));
priority_queue<pii,vector<pii>,greater<pii> > pq;
dist[T] = 0; pq.push({dist[T], T});
while(!pq.empty()) {
int u = pq.top().second; pq.pop();
if(vis[u]) continue;
vis[u] = 1;
for(int j=head[u]; j; j=E[j].next) {
int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost;
if(f < c && dist[v] > dist[u] + len) {
dist[v] = dist[u] + len;
pq.push({dist[v],v});
}
}
}
return dist[S] != LLINF;
}
ll DFS(int u, ll flow) {
if(u == T || flow == 0) return flow;
vis[u] = true; // differ from dinic
ll res = flow;
for(int j=head[u]; j; j=E[j].next) {
int v = E[j].v; ll f = E[j].flow, c = E[j].cap, len = E[j].cost;
if(!vis[v] && f < c && len == 0) { // not `dist[v] == dist[u]` ! they do not equal !
ll tmp = DFS(v, min(res, c-f)); // len = 0 <=> on the shortest path
E[j].flow += tmp;
E[j^1].flow -= tmp;
res -= tmp;
}
}
return flow - res;
}
void Augment() {
ll CurFlow = 0;
while(memset(vis, 0, sizeof(vis)),
(CurFlow = DFS(S, LLINF))) {
MaxFlow += CurFlow;
MinCost += CurFlow * delta;
}
}
void PrimalDual() {
if(!BellmanFord()) return;
Reduce(); Augment();
while(Dijkstra()) {
Reduce(); Augment();
}
}
struct record {
int u, v;
ll w, c;
}rec[MAXN];
int main() {
scanf("%d%d%d", &n, &m, &k);
S = 1, T = n;
for (int i = 1,u,v,c; i <= m; ++ i) {
scanf("%d%d%d%d", &rec[i].u, &rec[i].v, &rec[i].c, &rec[i].w);
AddEdge(rec[i].u, rec[i].v, rec[i].c, 0);
}
PrimalDual();
printf("%lld ", MaxFlow);
T = n+1; memset(head, 0, sizeof(head)), e_ptr = 1;
AddEdge(n, T, MaxFlow+k, 0);
for (int i = 1,u,v,c; i <= m; ++ i) {
AddEdge(rec[i].u, rec[i].v, rec[i].c, 0);
AddEdge(rec[i].u, rec[i].v, LLINF, rec[i].w);
}
ll rec_MinCost = MinCost;
PrimalDual();
printf("%lld\n", MinCost);
return 0;
}
View Code
P2770 航空路线问题
这道题的DFS遍历非常值得学习,因为必然是从 i + n 到 j 。
所以我们进入的点必然是 i + n 点, 出去的点必然是 j ,所以我们就直接跳到 j +n 就代表经过 j 点。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll, int> pii;
struct Edge {
int u, v;
ll flow, cap, cost;
int next;
};
const int MAXN = 5000, MAXM = 50000;
const ll LLINF = 0x3f3f3f3f3f3f3f3fLL;
int e_ptr = 1, S, T, n, m, head[MAXN+10]; Edge E[(MAXM+10)<<1];
ll dist[MAXN+10], MaxFlow, MinCost, delta;
int inq[MAXN+10], done[MAXN+10], vis[MAXN+10];
void AddEdge(int u, int v, ll cap, ll cost) {
E[++e_ptr] = (Edge) { u, v, 0, cap, cost, head[u] }; head[u] = e_ptr;
E[++e_ptr] = (Edge) { v, u, 0, 0, -cost, head[v] }; head[v] = e_ptr;
}
void Reduce() {
for(int i = 2; i <= e_ptr; i++)
E[i].cost += dist[E[i].v] - dist[E[i].u];
delta += dist[S];
}
bool BellmanFord() {
queue<int> Q;
memset(dist, 0x3f, sizeof(dist));
dist[T] = 0; Q.push(T); inq[T] = true;
while(!Q.empty()) {
int u = Q.front(); Q.pop(); inq[u] = false;
for(int j=head[u]; j; j=E[j].next) {
int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost;
if(f < c && dist[v] > dist[u] + len) {
dist[v] = dist[u] + len;
if(!inq[v]) {
inq[v] = true;
Q.push(v);
}
}
}
}
return dist[S] != LLINF;
}
bool Dijkstra() {
memset(dist, 0x3f, sizeof(dist));
memset(vis, 0, sizeof(vis));
priority_queue<pii,vector<pii>,greater<pii> > pq;
dist[T] = 0; pq.push({dist[T], T});
while(!pq.empty()) {
int u = pq.top().second; pq.pop();
if(vis[u]) continue;
vis[u] = 1;
for(int j=head[u]; j; j=E[j].next) {
int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost;
if(f < c && dist[v] > dist[u] + len) {
dist[v] = dist[u] + len;
pq.push({dist[v],v});
}
}
}
return dist[S] != LLINF;
}
ll DFS(int u, ll flow) {
if(u == T || flow == 0) return flow;
vis[u] = true; // differ from dinic
ll res = flow;
for(int j=head[u]; j; j=E[j].next) {
int v = E[j].v; ll f = E[j].flow, c = E[j].cap, len = E[j].cost;
if(!vis[v] && f < c && len == 0) { // not `dist[v] == dist[u]` ! they do not equal !
ll tmp = DFS(v, min(res, c-f)); // len = 0 <=> on the shortest path
E[j].flow += tmp;
E[j^1].flow -= tmp;
res -= tmp;
}
}
return flow - res;
}
void Augment() {
ll CurFlow = 0;
while(memset(vis, 0, sizeof(vis)),
(CurFlow = DFS(S, LLINF))) {
MaxFlow += CurFlow;
MinCost += CurFlow * delta;
}
}
void PrimalDual() {
if(!BellmanFord()) return;
Reduce(); Augment();
while(Dijkstra()) {
Reduce(); Augment();
}
}
map<string, int> name;
map<int, string> num;
int color[MAXN];
void DFS1(int u) {
color[u]=1;
cout << num[u-n] << endl;
for(int i=head[u];i;i=E[i].next)
if(E[i].v<=n&&E[i].cap > 0 && E[i].cap == E[i].flow) {
DFS1(E[i].v+n);
break;
}
}
void DFS2(int u) {
for(int i=head[u];i;i=E[i].next)
if(E[i].v<=n&&!color[E[i].v+n]&& E[i].cap == E[i].flow)
DFS2(E[i].v+n);
cout<<num[u-n]<<endl;
}
int main() {
cin >> n >> m;
S = 1, T = n+n;
int flag = 0;
AddEdge(S, 1+n, 2, -1), AddEdge(n, T, 2, -1);
for (int i = 1; i <= n; ++ i) {
string city; cin >> city;
name[city] = i, num[i] = city;
if (i == 1 || i == n) continue;
AddEdge(name[city], name[city]+n, 1, -1);
}
for (int i = 1; i <= m; ++ i) {
string u, v; cin >> u >> v;
if (name[u] > name[v]) swap(u, v);
AddEdge(name[u]+n, name[v], 1, 0);
if (name[u] == 1 && name[v] == n) flag = 1;
}
PrimalDual();
if (MaxFlow != 2 && !flag) printf("No Solution!\n");
else if (MaxFlow == 1 && flag) {
cout << 2 << endl;
cout << num[1] << endl << num[n] << endl << num[1] << endl;
}
else {
cout << -MinCost-2 << endl;
DFS1(n+1), DFS2(1+n);
}
return 0;
}
View Code
P3159 [CQOI2012]交换棋子
这道题主要需要注意的是这里是交换而不是移动,所以这里拆点方式就会很特别。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll, int> pii;
struct Edge {
int u, v;
ll flow, cap, cost;
int next;
};
const int MAXN = 5000+10, MAXM = 50000;
const ll LLINF = 0x3f3f3f3f3f3f3f3fLL;
int n, m, k;
int e_ptr = 1, S, T, head[MAXN]; Edge E[(MAXM)<<1];
ll dist[MAXN], MaxFlow, MinCost, delta;
int inq[MAXN], done[MAXN], vis[MAXN];
void AddEdge(int u, int v, ll cap, ll cost) {
E[++e_ptr] = (Edge) { u, v, 0, cap, cost, head[u] }; head[u] = e_ptr;
E[++e_ptr] = (Edge) { v, u, 0, 0, -cost, head[v] }; head[v] = e_ptr;
}
void Reduce() {
for(int i = 2; i <= e_ptr; i++)
E[i].cost += dist[E[i].v] - dist[E[i].u];
delta += dist[S];
}
bool BellmanFord() {
queue<int> Q;
memset(dist, 0x3f, sizeof(dist));
dist[T] = 0; Q.push(T); inq[T] = true;
while(!Q.empty()) {
int u = Q.front(); Q.pop(); inq[u] = false;
for(int j=head[u]; j; j=E[j].next) {
int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost;
if(f < c && dist[v] > dist[u] + len) {
dist[v] = dist[u] + len;
if(!inq[v]) {
inq[v] = true;
Q.push(v);
}
}
}
}
return dist[S] != LLINF;
}
bool Dijkstra() {
memset(dist, 0x3f, sizeof(dist));
memset(vis, 0, sizeof(vis));
priority_queue<pii,vector<pii>,greater<pii> > pq;
dist[T] = 0; pq.push({dist[T], T});
while(!pq.empty()) {
int u = pq.top().second; pq.pop();
if(vis[u]) continue;
vis[u] = 1;
for(int j=head[u]; j; j=E[j].next) {
int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost;
if(f < c && dist[v] > dist[u] + len) {
dist[v] = dist[u] + len;
pq.push({dist[v],v});
}
}
}
return dist[S] != LLINF;
}
ll DFS(int u, ll flow) {
if(u == T || flow == 0) return flow;
vis[u] = true; // differ from dinic
ll res = flow;
for(int j=head[u]; j; j=E[j].next) {
int v = E[j].v; ll f = E[j].flow, c = E[j].cap, len = E[j].cost;
if(!vis[v] && f < c && len == 0) { // not `dist[v] == dist[u]` ! they do not equal !
ll tmp = DFS(v, min(res, c-f)); // len = 0 <=> on the shortest path
E[j].flow += tmp;
E[j^1].flow -= tmp;
res -= tmp;
}
}
return flow - res;
}
void Augment() {
ll CurFlow = 0;
while(memset(vis, 0, sizeof(vis)),
(CurFlow = DFS(S, LLINF))) {
MaxFlow += CurFlow;
MinCost += CurFlow * delta;
}
}
void PrimalDual() {
if(!BellmanFord()) return;
Reduce(); Augment();
while(Dijkstra()) {
Reduce(); Augment();
}
}
char st[25][25];
char ed[25][25];
char times[25][25];
int dic[8][2] = {1,0, 0,1, -1,0, 0,-1, 1,1, 1,-1, -1,1, -1,-1};
int main() {
cin >> n >> m;
S = 0, T = n*m*3+1;
int cnt1 = 0, cnt2 = 0;
for (int i = 1; i <= n; ++ i)
for (int j = 1; j <= m; ++ j)
cin >> st[i][j], cnt1 += st[i][j]-'0';
for (int i = 1; i <= n; ++ i)
for (int j = 1; j <= m; ++ j)
cin >> ed[i][j], cnt2 += ed[i][j]-'0';
for (int i = 1; i <= n; ++ i)
for (int j = 1; j <= m; ++ j)
cin >> times[i][j];
// 不加就是pre , + n*m就是mid, + 2*n*m就是aft
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j <= m; ++ j) {
int id = (i-1)*m+j;
int limit = times[i][j]-'0';
if (st[i][j] == '1') AddEdge(S, id+n*m, 1, 0);
if (ed[i][j] == '1') AddEdge(id+n*m, T, 1, 0);
if (st[i][j] == '0' && ed[i][j] == '1')
AddEdge(id, id+n*m, (limit+1)/2, 0), AddEdge(id+n*m, id+2*n*m, limit/2, 0);
else if (st[i][j] == '1' && ed[i][j] == '0')
AddEdge(id, id+n*m, limit/2, 0), AddEdge(id+n*m, id+2*n*m, (limit+1)/2, 0);
else
AddEdge(id, id+n*m, limit/2, 0), AddEdge(id+n*m, id+2*n*m, limit/2, 0);
for (int k = 0; k < 8; ++ k) {
int tx = i+dic[k][0], ty = j+dic[k][1];
if (tx < 1 || ty < 1 || tx > n || ty > m) continue;
int id1 = (tx-1)*m + ty;
AddEdge(id+2*n*m, id1, LLINF, 1);
}
}
}
PrimalDual();
if (cnt1 != cnt2 || MaxFlow != cnt1) cout << -1 << endl;
else cout << MinCost << endl;
return 0;
}
View Code
P3356 火星探险问题
主要就是路径输出,看来我已经掌握了路径输出的要点了。
这道题因为没有用最大费用导致debug了好久。好累。
今天把讨厌的人删了,还是挺舒服的。
建图不是很难,我都想到了
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll, int> pii;
struct Edge {
int u, v;
ll flow, cap, cost;
int next;
};
const int MAXN = 5000+10, MAXM = 50000;
const ll LLINF = 0x3f3f3f3f3f3f3f3fLL;
int n, m, k;
int e_ptr = 1, S, T, head[MAXN]; Edge E[(MAXM)<<1];
ll dist[MAXN], MaxFlow, MinCost, delta;
int inq[MAXN], done[MAXN], vis[MAXN];
void AddEdge(int u, int v, ll cap, ll cost) {
E[++e_ptr] = (Edge) { u, v, 0, cap, cost, head[u] }; head[u] = e_ptr;
E[++e_ptr] = (Edge) { v, u, 0, 0, -cost, head[v] }; head[v] = e_ptr;
}
void Reduce() {
for(int i = 2; i <= e_ptr; i++)
E[i].cost += dist[E[i].v] - dist[E[i].u];
delta += dist[S];
}
bool BellmanFord() {
queue<int> Q;
memset(dist, 0x3f, sizeof(dist));
dist[T] = 0; Q.push(T); inq[T] = true;
while(!Q.empty()) {
int u = Q.front(); Q.pop(); inq[u] = false;
for(int j=head[u]; j; j=E[j].next) {
int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost;
if(f < c && dist[v] > dist[u] + len) {
dist[v] = dist[u] + len;
if(!inq[v]) {
inq[v] = true;
Q.push(v);
}
}
}
}
return dist[S] != LLINF;
}
bool Dijkstra() {
memset(dist, 0x3f, sizeof(dist));
memset(vis, 0, sizeof(vis));
priority_queue<pii,vector<pii>,greater<pii> > pq;
dist[T] = 0; pq.push({dist[T], T});
while(!pq.empty()) {
int u = pq.top().second; pq.pop();
if(vis[u]) continue;
vis[u] = 1;
for(int j=head[u]; j; j=E[j].next) {
int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost;
if(f < c && dist[v] > dist[u] + len) {
dist[v] = dist[u] + len;
pq.push({dist[v],v});
}
}
}
return dist[S] != LLINF;
}
ll DFS(int u, ll flow) {
if(u == T || flow == 0) return flow;
vis[u] = true; // differ from dinic
ll res = flow;
for(int j=head[u]; j; j=E[j].next) {
int v = E[j].v; ll f = E[j].flow, c = E[j].cap, len = E[j].cost;
if(!vis[v] && f < c && len == 0) { // not `dist[v] == dist[u]` ! they do not equal !
ll tmp = DFS(v, min(res, c-f)); // len = 0 <=> on the shortest path
E[j].flow += tmp;
E[j^1].flow -= tmp;
res -= tmp;
}
}
return flow - res;
}
void Augment() {
ll CurFlow = 0;
while(memset(vis, 0, sizeof(vis)),
(CurFlow = DFS(S, LLINF))) {
MaxFlow += CurFlow;
MinCost += CurFlow * delta;
}
}
void PrimalDual() {
if(!BellmanFord()) return;
Reduce(); Augment();
while(Dijkstra()) {
Reduce(); Augment();
}
}
int g[36][36];
int dic[2][2] = {1,0, 0,1};
void FindPath(int tms, int u) {
for (int i = head[u]; i; i = E[i].next) {
int v = E[i].v;
if (v == u-n*m) continue;
if (v == T || v == S) continue;
if (!E[i].flow) continue;
E[i].flow--;
int pos = (v == u-n*m+1);
cout << tms << " " << pos << endl;
FindPath(tms, v+n*m);
break;
}
}
int main() {
int num; scanf("%d", &num);
scanf("%d%d", &m, &n);
S = 0, T = n*m*2+1;
for (int i = 1; i <= n; ++ i)
for (int j = 1; j <= m; ++ j)
scanf("%d", &g[i][j]);
AddEdge(S, 1, num, 0), AddEdge(n*m*2, T, LLINF, 0);
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j <= m; ++ j) {
int id = (i-1)*m+j;
AddEdge(id, id+n*m, LLINF, 0);
if (g[i][j] == 1) continue ;
if (g[i][j] == 2) AddEdge(id, id+n*m, 1, -1);
for (int k = 0; k < 2; ++ k) {
int tx = i+dic[k][0], ty = j+dic[k][1];
int _id = (tx-1)*m+ty;
if (ty > m || tx > n || g[tx][ty] == 1) continue;
AddEdge(id+n*m, _id, LLINF, 0);
}
}
}
PrimalDual();
for (int i = 1; i <= MaxFlow; ++ i)
FindPath(i, 1+n*m);
return 0;
}
View Code
P3440 [POI2006]SZK-Schools
看数据范围就知道很暴力。直接将200范围全部拆点,暴力连就好了。跑出来最大流不为n就输出NIE。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll, int> pii;
struct Edge {
int u, v;
ll flow, cap, cost;
int next;
};
const int MAXN = 5000+10, MAXM = 50000;
const ll LLINF = 0x3f3f3f3f3f3f3f3fLL;
int n, m, k;
int e_ptr = 1, S, T, head[MAXN]; Edge E[(MAXM)<<1];
ll dist[MAXN], MaxFlow, MinCost, delta;
int inq[MAXN], done[MAXN], vis[MAXN];
void AddEdge(int u, int v, ll cap, ll cost) {
E[++e_ptr] = (Edge) { u, v, 0, cap, cost, head[u] }; head[u] = e_ptr;
E[++e_ptr] = (Edge) { v, u, 0, 0, -cost, head[v] }; head[v] = e_ptr;
}
void Reduce() {
for(int i = 2; i <= e_ptr; i++)
E[i].cost += dist[E[i].v] - dist[E[i].u];
delta += dist[S];
}
bool BellmanFord() {
queue<int> Q;
memset(dist, 0x3f, sizeof(dist));
dist[T] = 0; Q.push(T); inq[T] = true;
while(!Q.empty()) {
int u = Q.front(); Q.pop(); inq[u] = false;
for(int j=head[u]; j; j=E[j].next) {
int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost;
if(f < c && dist[v] > dist[u] + len) {
dist[v] = dist[u] + len;
if(!inq[v]) {
inq[v] = true;
Q.push(v);
}
}
}
}
return dist[S] != LLINF;
}
bool Dijkstra() {
memset(dist, 0x3f, sizeof(dist));
memset(vis, 0, sizeof(vis));
priority_queue<pii,vector<pii>,greater<pii> > pq;
dist[T] = 0; pq.push({dist[T], T});
while(!pq.empty()) {
int u = pq.top().second; pq.pop();
if(vis[u]) continue;
vis[u] = 1;
for(int j=head[u]; j; j=E[j].next) {
int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost;
if(f < c && dist[v] > dist[u] + len) {
dist[v] = dist[u] + len;
pq.push({dist[v],v});
}
}
}
return dist[S] != LLINF;
}
ll DFS(int u, ll flow) {
if(u == T || flow == 0) return flow;
vis[u] = true; // differ from dinic
ll res = flow;
for(int j=head[u]; j; j=E[j].next) {
int v = E[j].v; ll f = E[j].flow, c = E[j].cap, len = E[j].cost;
if(!vis[v] && f < c && len == 0) { // not `dist[v] == dist[u]` ! they do not equal !
ll tmp = DFS(v, min(res, c-f)); // len = 0 <=> on the shortest path
E[j].flow += tmp;
E[j^1].flow -= tmp;
res -= tmp;
}
}
return flow - res;
}
void Augment() {
ll CurFlow = 0;
while(memset(vis, 0, sizeof(vis)),
(CurFlow = DFS(S, LLINF))) {
MaxFlow += CurFlow;
MinCost += CurFlow * delta;
}
}
void PrimalDual() {
if(!BellmanFord()) return;
Reduce(); Augment();
while(Dijkstra()) {
Reduce(); Augment();
}
}
struct Node {
} sch[205];
int main() {
int n; scanf("%d", &n);
S = 0, T = 200+n+1;
for (int i = 1; i <= n; ++ i) {
int mi, ai, bi, ki;
scanf("%d %d %d %d", &mi, &ai, &bi, &ki);
for (int j = ai; j <= bi; ++ j)
AddEdge(i, n+j, 1, ki*abs(j-mi));
AddEdge(S, i, 1, 0);
}
for (int i = 1; i <= 200; ++ i) AddEdge(n+i, T, 1, 0);
PrimalDual();
if (MaxFlow != n) cout << "NIE" << endl;
else cout << MinCost << endl;
return 0;
}
View Code
P3705 [SDOI2017]新生舞会
我的原始对偶板子好像不是很适合求这种分数规划费用流的题目,这算是给个板子吧。思想是不难的。
#include<bits/stdc++.h>
using namespace std;
int read() {
char cc = getchar(); int cn = 0, flus = 1;
while(cc < '0' || cc > '9') { if( cc == '-' ) flus = -flus; cc = getchar(); }
while(cc >= '0' && cc <= '9') cn = cn * 10 + cc - '0', cc = getchar();
return cn * flus;
}
const int M = 2e5 + 5 ;
const int N = 200 + 5 ;
#define inf 123456789
#define eps 1e-8
#define rep( i, s, t ) for( register int i = s; i <= t; ++ i )
#define Next( i, x ) for( register int i = head[x]; i; i = e[i].next )
struct E {
int to, next, w;
double f ;
} e[M * 2];
int head[N * 2], cur[N * 2], cnt, n, mark[N * 2], vis[N * 2], s, t ;
double dis[N * 2], Ans, a[N][N], b[N][N] ;
void add( int x, int y, int z, double f ) {
e[++ cnt] = (E){ y, head[x], z, f }, head[x] = cnt ;
e[++ cnt] = (E){ x, head[y], 0, -f }, head[y] = cnt ;
}
queue< int > q;
bool spfa() {
rep( i, s, t ) dis[i] = - inf ;
memset( vis, 0, sizeof(vis) ) ;
q.push(s) ; dis[s] = 0 ;
while( !q.empty() ) {
int u = q.front() ; q.pop() ; vis[u] = 0;
Next( i, u ) {
int v = e[i].to ;
if( dis[v] < dis[u] + e[i].f && e[i].w ) {
dis[v] = dis[u] + e[i].f;
if( !vis[v] ) q.push(v), vis[v] = 1;
}
}
}
return dis[t] != -inf ;
}
int dfs( int x, int dist ) {
mark[x] = 1 ;
if( x == t ) return dist ;
int flow = 0 ;
for( register int &i = cur[x]; i; i = e[i].next ) {
int v = e[i].to ;
if( !mark[v] && dis[v] == dis[x] + e[i].f && e[i].w ) {
int di = dfs( v, min( dist, e[i].w ) ) ;
if( di > 0 ) {
e[i].w -= di, e[i ^ 1].w += di;
flow += di, dist -= di ;
Ans += di * e[i].f ;
if( dist == 0 ) return flow ;
}
}
}
return flow ;
}
void zkwflow() {
Ans = 0;
while(spfa()) {
memcpy( cur, head, sizeof(head) ) ;
mark[t] = 1 ;
while( mark[t] ) {
memset( mark, 0, sizeof(mark) ) ;
dfs( s, inf ) ;
}
}
}
bool check( double x ) {
s = 0, t = 2 * n + 1 ; cnt = 1;
memset( head, 0, sizeof(head) );
rep( i, 1, n ) add( s, i, 1, 0 ), add( i + n, t, 1, 0 ) ;
rep( i, 1, n ) rep( j, 1, n ) add( i, j + n, 1, a[i][j] - x * b[i][j] );
zkwflow() ;
return Ans >= 0 ;
}
void solve() {
double l = 0, r = 10000 + 5, mid, ans = 0 ;
while( r - l > eps ) {
mid = ( l + r ) / 2.0 ;
if( check(mid) ) ans = mid, l = mid + eps ;
else r = mid - eps ;
}
printf("%.6lf\n", ans ) ;
}
signed main()
{
n = read() ;
rep( i, 1, n ) rep( j, 1, n ) a[i][j] = read() ;
rep( i, 1, n ) rep( j, 1, n ) b[i][j] = read() ;
solve() ;
return 0;
}
View Code
[TJOI2013]循环格
这道题主要是要发现出点和入点的关系,之后需要注意的点就是,这个方向是可以出图的,就是不用判边界,而是要取模。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll, int> pii;
struct Edge {
int u, v;
ll flow, cap, cost;
int next;
};
const int MAXN = 5000+10, MAXM = 50000;
const ll LLINF = 0x3f3f3f3f3f3f3f3fLL;
int n, m, k;
int e_ptr = 1, S, T, head[MAXN]; Edge E[(MAXM)<<1];
ll dist[MAXN], MaxFlow, MinCost, delta;
int inq[MAXN], done[MAXN], vis[MAXN];
void AddEdge(int u, int v, ll cap, ll cost) {
E[++e_ptr] = (Edge) { u, v, 0, cap, cost, head[u] }; head[u] = e_ptr;
E[++e_ptr] = (Edge) { v, u, 0, 0, -cost, head[v] }; head[v] = e_ptr;
}
void Reduce() {
for(int i = 2; i <= e_ptr; i++)
E[i].cost += dist[E[i].v] - dist[E[i].u];
delta += dist[S];
}
bool BellmanFord() {
queue<int> Q;
memset(dist, 0x3f, sizeof(dist));
dist[T] = 0; Q.push(T); inq[T] = true;
while(!Q.empty()) {
int u = Q.front(); Q.pop(); inq[u] = false;
for(int j=head[u]; j; j=E[j].next) {
int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost;
if(f < c && dist[v] > dist[u] + len) {
dist[v] = dist[u] + len;
if(!inq[v]) {
inq[v] = true;
Q.push(v);
}
}
}
}
return dist[S] != LLINF;
}
bool Dijkstra() {
memset(dist, 0x3f, sizeof(dist));
memset(vis, 0, sizeof(vis));
priority_queue<pii,vector<pii>,greater<pii> > pq;
dist[T] = 0; pq.push({dist[T], T});
while(!pq.empty()) {
int u = pq.top().second; pq.pop();
if(vis[u]) continue;
vis[u] = 1;
for(int j=head[u]; j; j=E[j].next) {
int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost;
if(f < c && dist[v] > dist[u] + len) {
dist[v] = dist[u] + len;
pq.push({dist[v],v});
}
}
}
return dist[S] != LLINF;
}
ll DFS(int u, ll flow) {
if(u == T || flow == 0) return flow;
vis[u] = true; // differ from dinic
ll res = flow;
for(int j=head[u]; j; j=E[j].next) {
int v = E[j].v; ll f = E[j].flow, c = E[j].cap, len = E[j].cost;
if(!vis[v] && f < c && len == 0) { // not `dist[v] == dist[u]` ! they do not equal !
ll tmp = DFS(v, min(res, c-f)); // len = 0 <=> on the shortest path
E[j].flow += tmp;
E[j^1].flow -= tmp;
res -= tmp;
}
}
return flow - res;
}
void Augment() {
ll CurFlow = 0;
while(memset(vis, 0, sizeof(vis)),
(CurFlow = DFS(S, LLINF))) {
MaxFlow += CurFlow;
MinCost += CurFlow * delta;
}
}
void PrimalDual() {
if(!BellmanFord()) return;
Reduce(); Augment();
while(Dijkstra()) {
Reduce(); Augment();
}
}
map<char, int> DicHash;
int g[20][20];
int dic[4][2] = {-1,0, 1,0, 0,-1, 0,1};
int main() {
DicHash['U'] = 0, DicHash['D'] = 1;
DicHash['L'] = 2, DicHash['R'] = 3;
cin >> n >> m;
S = 0, T = n*m*2+1;
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j <= m; ++ j) {
char ch; cin >> ch, g[i][j] = DicHash[ch];
int id = (i-1)*m+j;
AddEdge(S, id, 1, 0), AddEdge(id+n*m, T, 1, 0);
for (int k = 0; k < 4; ++ k) {
int tx = (i+dic[k][0]+n-1)%n+1, ty = (dic[k][1]+j+m-1)%m+1;
int _id = (tx-1)*m+ty;
AddEdge(id, _id+n*m, 1, g[i][j] != k);
}
}
}
PrimalDual();
cout << MinCost << endl;
return 0;
}
View Code