网络流
网络流
记得我说过,除了树剖之外不再学超纲算法,然而今天我又...学了网络流。
Upd:联赛考完了,可以学了,咕咕咕。
学了Dinic算法,每次先做一次bfs求出是否还有增广路,再进行一次dfs看看最大的增广路是多少。对于每条边建一条容量为0的反向边,便于反悔。有的地方其实还不是很明白,以后再仔细想想。
首先是定义和性质:
网络:网络是一种特殊的有向图,每条边带一个数字,叫做流量,意为从起点到终点最多通过$n$的单位的流。两个特殊的点,源点S,从这里可以运出无限的流;汇点T,流最后都会到这里来。
最大流:从源点到汇点最多可以运送的流量。
割:如果将图上割去一些边可以使得源点汇点不连通,那么这些边所构成的集合叫做一个割;
最小割:所有的割中流量和最小的那个。
最大流的三个性质:
容量限制:$f(u,v)<=c(u,v)$.
斜对称性:$f(u,v)=-f(v,u)$.
流量平衡:除了源汇节点,每个点的流入量和流出量相同.
至于这三个性质有什么用...我也不清楚...
最小割最大流定理:
最大流=最小割,证明:
刚刚机房停电了!!!我写好的一长段都没了啊啊啊啊啊啊
首先最小割不能小于最大流,否则无法将最大流割断。
其次最大流中的每个流量至少会经过一条满流的边,否则可以沿着这条路线再加流,只要把这些边都割断就可以达到割的目的了,所以最小割可以等于最大流。
综上所述最大流等于最小割。
费用流:每条边还有一个附加权表示每从这条边上运一个流所需要付出的代价。
最小费用最大流:在最大流的基础上费用最小。
接下来是板子:
网络最大流:https://www.luogu.org/problemnew/show/P3376
1 // luogu-judger-enable-o2
2 # include <cstdio>
3 # include <iostream>
4 # include <queue>
5 # include <cstring>
6 # define R register int
7 # define inf 1e8
8
9 const int maxn=20009;
10 int u,v,w,n,m,s,t,h=1,firs[maxn],d[maxn];
11 bool vis[maxn];
12 std::queue <int> q;
13 struct edge
14 {
15 int too,nex,cap,flow;
16 }g[1000009];
17
18 int min(int a,int b)
19 {
20 if(a<b) return a;
21 return b;
22 }
23
24 int read()
25 {
26 int x=0;
27 char c=getchar();
28 while (!isdigit(c))
29 c=getchar();
30 while (isdigit(c))
31 {
32 x=(x<<3)+(x<<1)+(c^48);
33 c=getchar();
34 }
35 return x;
36 }
37
38 void add(int x,int y,int v)
39 {
40 g[++h].too=y;
41 g[h].nex=firs[x];
42 firs[x]=h;
43 g[h].cap=v;
44 g[h].flow=0;
45 }
46
47 bool bfs()
48 {
49 memset(vis,0,sizeof(vis));
50 while (q.size()) q.pop();
51 q.push(s);
52 d[s]=0;
53 vis[s]=true;
54 int beg,j;
55 while (q.size())
56 {
57 beg=q.front();
58 q.pop();
59 for (R i=firs[beg];i;i=g[i].nex)
60 {
61 j=g[i].too;
62 if(g[i].cap<=g[i].flow) continue;
63 if(vis[j]) continue;
64 vis[j]=true;
65 d[j]=d[beg]+1;
66 q.push(j);
67 }
68 }
69 return vis[t];
70 }
71
72 int dfs(int x,int minf)
73 {
74 if(x==t||minf==0) return minf;
75 int j,flow=0,f;
76 for (R i=firs[x];i;i=g[i].nex)
77 {
78 j=g[i].too;
79 if(d[j]!=d[x]+1) continue;
80 f=dfs(j,min(minf,g[i].cap-g[i].flow));
81 if(f<=0) continue;
82 g[i].flow+=f;
83 g[i^1].flow-=f;
84 minf-=f;
85 flow+=f;
86 if(minf==0) break;
87 }
88 return flow;
89 }
90
91 int maxflow()
92 {
93 int ans=0;
94 while (bfs())
95 ans+=dfs(s,inf);
96 return ans;
97 }
98
99 int main()
100 {
101 scanf("%d%d%d%d",&n,&m,&s,&t);
102 for (R i=1;i<=m;++i)
103 {
104 u=read(),v=read(),w=read();
105 add(u,v,w);
106 add(v,u,0);
107 }
108 printf("%d",maxflow());
109 return 0;
110 }
最小费用最大流:https://www.luogu.org/problemnew/show/P3381
似乎都用EK写的,还没有见过Dinic的版本,那就跟风一回好了。其实就是把BFS增广改成了最短路增广。
1 # include <cstdio>
2 # include <iostream>
3 # include <cstring>
4 # include <queue>
5 # define R register int
6 # define inf 999999999
7
8 using namespace std;
9
10 const int maxn=5005;
11 const int maxm=50005;
12 int n,m,s,t,x,y,w,co,h=1,min_cos,flow,pre[maxn],d[maxn],Fl[maxn];
13 bool in_que[maxn],vis[maxn];
14 queue <int> q;
15 int firs[maxn];
16 struct edge
17 {
18 int too,nex,cap,flow,co;
19 }g[maxm<<1];
20
21 int read()
22 {
23 int x=0;
24 char c=getchar();
25 while (!isdigit(c))
26 c=getchar();
27 while (isdigit(c))
28 {
29 x=(x<<3)+(x<<1)+(c^48);
30 c=getchar();
31 }
32 return x;
33 }
34
35 void add (int x,int y,int cap,int co)
36 {
37 g[++h].too=y;
38 g[h].cap=cap;
39 g[h].co=co;
40 g[h].nex=firs[x];
41 firs[x]=h;
42 }
43
44 bool bfs ()
45 {
46 memset(vis,0,sizeof(vis));
47 memset(d,0x3f,sizeof(d));
48 memset(in_que,0,sizeof(in_que));
49 vis[s]=true;
50 d[s]=0;
51 int j,beg;
52 Fl[s]=inf;
53 q.push(s);
54 while (q.size())
55 {
56 beg=q.front();
57 q.pop();
58 in_que[beg]=false;
59 for (R i=firs[beg];i;i=g[i].nex)
60 {
61 j=g[i].too;
62 if(g[i].cap<=g[i].flow) continue;
63 if(d[j]<=d[beg]+g[i].co) continue;
64 vis[j]=true;
65 d[j]=d[beg]+g[i].co;
66 Fl[j]=min(Fl[beg],g[i].cap-g[i].flow);
67 pre[j]=i;
68 if(!in_que[j]) q.push(j),in_que[j]=true;
69 }
70 }
71 return vis[t];
72 }
73
74 void dfs ()
75 {
76 int x=t;
77 while (x!=s)
78 {
79 int i=pre[x];
80 g[i].flow+=Fl[t];
81 g[i^1].flow-=Fl[t];
82 x=g[i^1].too;
83 }
84 flow+=Fl[t];
85 min_cos+=d[t]*Fl[t];
86 }
87
88 void maxflow()
89 {
90 while (bfs())
91 dfs();
92 }
93
94 int main()
95 {
96 scanf("%d%d%d%d",&n,&m,&s,&t);
97 for (R i=1;i<=m;++i)
98 {
99 x=read(),y=read(),w=read(),co=read();
100 add(x,y,w,co);
101 add(y,x,0,-co);
102 }
103 maxflow();
104 printf("%d %d",flow,min_cos);
105 return 0;
106 }
还有一些常用的套路:
多源多汇:建立超级源点和汇点,向所有的源汇连流量为正无穷的边。
点限流:每个点拆成出点和入点,之间连接流量为点限度的边。
有一些东西其实已经可以出成题考建模技巧了,但是由于适用范围比较广,往往被当做模板来用...
网络流的推广:
一般所说的网络流中,每条边只有一个流量上界,但是也可以将它认为是带有流量下界且下界为0的一种特殊情况。
带上下界的循环流看了一小时了也没懂,先看一个简单一点的。
满足供需关系的可行流:每个点有一个权 $w(x)$ ,之前的流量平衡条件改为 出=入+$w(x)$ ,这个问题可以这样考虑,将权为正的点认为是凭空多出来 $w(x)$ 的流量,对于权为负数的点,将 $w(x)$ 的流量消掉,这样还是满足流量平衡。问题转化到这里就变成了一个多源多汇问题。如果存在一种方案使得附加边全部满流即存在可行流。
形式化的讲(配合后面的格式),
$$w(i)=\sum_{(i,j)\in E}f(i,j)-\sum_{(j,i)\in E}f(j,i)$$
我现在明白网络流三条看起来很显然的性质的作用了,就是用来以此为根据调整建图策略。
非常神奇的是这个算法可以转化为循环流:
---以下内容摘自《习题指导》
$$\sum_{(i,j)\in E} f(i,j)-\sum_{(j,i)\in E}f(j,i) =0\quad \forall i \in V $$
$$b(i,j)<=f(i,j)<=c(i,j) \quad \forall i \in V$$
这是上下界网络流的定义;
既然我们不会处理下界,不如定义一个新的函数,通过变换消掉下界,$g(i,j)=f(i,j)-b(i,j)$
$$\sum_{(i,j)\in E} g(i,j)-\sum_{(j,i)\in E}g(j,i)=\sum_{(j,i)\in E} b(j,i)-\sum_{(i,j)\in E} b(i,j) \quad \forall i \in V $$
$$0<=g(i,j)<=c(i,j)-b(i,j) \quad \forall i \in V$$
这时候可以发现,虽然等式左边还是未知量,但是等式右边已经是一个常数了呀,和上一道题的权没有什么区别。于是就可以做了,真的好神奇啊。如果没有看懂也没关系,下面还有讲。
费用与流量有关的费用流:拆边,进行边权差分...还是举个例子吧:
像这样边权不固定的问题是比较难办的,但是由于流量上界比较小,可以拆边:
因为求的是最小费用,必然是第一个流量走上面那条边,越靠后的流量走边权越大的边,达到了所需要的效果.
套路先说这么多,做一点题再补.
最后来看一些题:
1.负载平衡:https://www.luogu.org/problemnew/show/P4016
题意概述:G公司有n个沿铁路运输线环形排列的仓库,每个仓库存储的货物数量不等。如何用最少搬运量可以使n个仓库的库存数量相同。搬运货物时,只能在相邻的仓库之间搬运。
其实就是环形的均分纸牌。
感觉我的建图挺正常的,但是网上好像都不是这么建的...
弄一个源点向每个仓库连边,流量为原数量,费用为0;(因为本来就在那里嘛)
每个仓库向汇点连边,流量为平均数,费用为0;(也就是说要达到最大流必须每个点都弄到平均数个货物)
相邻的仓库连一下边,流量无限,费用为1;(自己随便传去吧)
1 // luogu-judger-enable-o2
2 # include <cstdio>
3 # include <iostream>
4 # include <cstring>
5 # include <queue>
6 # define R register int
7 # define inf 999999999
8
9 using namespace std;
10
11 const int maxn=105;
12 const int maxm=409;
13 int n,m,s,t,x,y,w,co,h=1,min_cos,flow,pre[maxn],d[maxn],Fl[maxn];
14 long long S=0;
15 bool in_que[maxn],vis[maxn];
16 queue <int> q;
17 int firs[maxn];
18 struct edge
19 {
20 int too,nex,cap,flow,co;
21 }g[maxm<<1];
22
23 int read()
24 {
25 int x=0;
26 char c=getchar();
27 while (!isdigit(c))
28 c=getchar();
29 while (isdigit(c))
30 {
31 x=(x<<3)+(x<<1)+(c^48);
32 c=getchar();
33 }
34 return x;
35 }
36
37 void add (int x,int y,int cap,int co)
38 {
39 g[++h].too=y;
40 g[h].cap=cap;
41 g[h].co=co;
42 g[h].nex=firs[x];
43 firs[x]=h;
44 }
45
46 bool bfs ()
47 {
48 memset(vis,0,sizeof(vis));
49 memset(d,0x3f,sizeof(d));
50 memset(in_que,0,sizeof(in_que));
51 vis[s]=true;
52 d[s]=0;
53 int j,beg;
54 Fl[s]=inf;
55 q.push(s);
56 while (q.size())
57 {
58 beg=q.front();
59 q.pop();
60 in_que[beg]=false;
61 for (R i=firs[beg];i;i=g[i].nex)
62 {
63 j=g[i].too;
64 if(g[i].cap<=g[i].flow) continue;
65 if(d[j]<=d[beg]+g[i].co) continue;
66 vis[j]=true;
67 d[j]=d[beg]+g[i].co;
68 Fl[j]=min(Fl[beg],g[i].cap-g[i].flow);
69 pre[j]=i;
70 if(!in_que[j]) q.push(j),in_que[j]=true;
71 }
72 }
73 return vis[t];
74 }
75
76 void dfs ()
77 {
78 int x=t;
79 while (x!=s)
80 {
81 int i=pre[x];
82 g[i].flow+=Fl[t];
83 g[i^1].flow-=Fl[t];
84 x=g[i^1].too;
85 }
86 flow+=Fl[t];
87 min_cos+=d[t]*Fl[t];
88 }
89
90 void maxflow()
91 {
92 while (bfs())
93 dfs();
94 }
95
96 int main()
97 {
98 scanf("%d",&n);
99 t=n+1;
100 for (int i=1;i<=n;++i)
101 {
102 x=read();
103 S+=x;
104 add(s,i,x,0);
105 add(i,s,0,0);
106 if(i==n) continue;
107 add(i,i+1,inf,1);
108 add(i+1,i,0,-1);
109 add(i+1,i,inf,1);
110 add(i,i+1,0,-1);
111 }
112 add(n,1,inf,1);
113 add(1,n,0,-1);
114 add(1,n,inf,1);
115 add(n,1,0,-1);
116 for (int i=1;i<=n;++i)
117 add(i,t,S/n,0),add(t,i,0,0);
118 maxflow();
119 printf("%d",min_cos);
120 return 0;
121 }
2.拍照:https://www.luogu.org/problemnew/show/P3410
神题一道,神题一道。其实也是个套路题,只不过没见过的时候觉得非常难想。
题意概述:给定m个集合,每个集合中包含n个元素中的一些。如果某个集合中的元素被全选,会有一个收益,选每个元素也会有各自的花费,求最大收益。$n,m<=100$
看到这么小的数据范围就觉得是dp,似乎可以bitset强行状压一番,然而这是不行的...统计答案也挺麻烦的。
其实这是一种网络流的模型:最大权闭合子图;
从这道题其实可以看出来一个很显然的事情,一个集合的收益可以获得,当且仅当它里面的元素全被选。(这话说出来怎么这么像机翻)
先连一下图,再看为什么:
对于每一个集合,建一个虚点,从源点向它连一条流量为收益的边;这个虚点向集合中的每个元素连一条流量为inf的边;每个元素向汇点连一条流量为花费的边,求最小割。
为什么?首先假设每个集合的收益都可以被获得,再考虑哪些收益不拿更优。这个图是一个挺明显的四层图:
假设被留下的东西连在s,不选的连在t。那么想要把图变得不连通,就要求浅蓝色的点必须选择一方,有两种方案:
·把橙色边割断,表示不要这个收益了。
·把它发出去的所有绿色边割断,表示它涉及的所有粉色点都选。
浅蓝色的边是不能碰的,因为集合里有哪些元素不是我们能够左右的...这样我倒是又想到一道题:可以花费$c_i$的代价把某个点从某个集合中去掉,这样这个边也有边权了,不过这个题好像也没啥意思。
答案即为总收益减去最小割。注意前向星的h要从1开始...
1 # include <cstdio>
2 # include <iostream>
3 # include <queue>
4 # include <cstring>
5 # define R register int
6 # define inf 0x7FFFFFFF
7
8 using namespace std;
9
10 int s,t,n,h=1,m,co,x,ans;
11 bool vis[205];
12 int firs[205],d[205];
13 queue <int> q;
14 struct edge
15 {
16 int too,nex,flow,cap;
17 }g[30000];
18
19 void add (int x,int y,int co)
20 {
21 g[++h].too=y;
22 g[h].nex=firs[x];
23 firs[x]=h;
24 g[h].cap=co;
25 g[h].flow=0;
26 }
27
28 bool bfs()
29 {
30 memset(vis,0,sizeof(vis));
31 q.push(s);
32 d[s]=0;
33 vis[s]=true;
34 int beg,j;
35 while (q.size())
36 {
37 beg=q.front();
38 q.pop();
39 for (R i=firs[beg];i;i=g[i].nex)
40 {
41 j=g[i].too;
42 if(g[i].cap<=g[i].flow) continue;
43 if(vis[j]) continue;
44 vis[j]=true;
45 d[j]=d[beg]+1;
46 q.push(j);
47 }
48 }
49 return vis[t];
50 }
51
52 int dfs (int x,int minf)
53 {
54 if(x==t||minf==0) return minf;
55 int j,flow=0,f;
56 for (R i=firs[x];i;i=g[i].nex)
57 {
58 j=g[i].too;
59 if(d[j]!=d[x]+1) continue;
60 f=dfs(j,min(minf,g[i].cap-g[i].flow));
61 if(f<=0) continue;
62 g[i].flow+=f;
63 g[i^1].flow-=f;
64 minf-=f;
65 flow+=f;
66 if(minf==0) break;
67 }
68 return flow;
69 }
70
71 int maxflow()
72 {
73 int flow=0;
74 while (bfs())
75 flow+=dfs(s,inf);
76 return flow;
77 }
78
79 int main()
80 {
81 scanf("%d%d",&m,&n);
82 s=0,t=n+m+1;
83 for (R i=1;i<=m;++i)
84 {
85 scanf("%d%d",&co,&x);
86 add(s,i+n,co);
87 add(i+n,s,0);
88 ans+=co;
89 while (x)
90 {
91 add(i+n,x,inf);
92 add(x,i+n,0);
93 scanf("%d",&x);
94 }
95 }
96 for (R i=1;i<=n;++i)
97 {
98 scanf("%d",&x);
99 add(i,t,x);
100 add(t,i,0);
101 }
102 printf("%d",ans-maxflow());
103 return 0;
104 }
3.太空飞行计划问题:https://www.luogu.org/problemnew/show/P2762
题意概述:和上边那个题几乎一样,但是还要输出方案。
输出方案简直毒瘤啊。不过其实思路非常妙。从刚刚的解题思路考虑,其实最后保留下来的点都是可以从s到达的,也就是...那个vis数组...
1 # include <cstdio>
2 # include <iostream>
3 # include <queue>
4 # include <cstring>
5 # include <algorithm>
6 # define R register int
7 # define inf 0x7fffffff
8
9 using namespace std;
10
11 int s,t,n,h=1,m,co,x,ans;
12 bool vis[105];
13 int firs[105],d[105],sta[105],Top=0;
14 queue <int> q;
15 struct edge
16 {
17 int too,nex,flow,cap;
18 }g[30000];
19
20 void add (int x,int y,int co)
21 {
22 g[++h].too=y;
23 g[h].nex=firs[x];
24 firs[x]=h;
25 g[h].cap=co;
26 g[h].flow=0;
27 }
28
29 bool bfs()
30 {
31 memset(vis,0,sizeof(vis));
32 while (q.size()) q.pop();
33 q.push(s);
34 d[s]=0;
35 vis[s]=true;
36 int beg,j;
37 while (q.size())
38 {
39 beg=q.front();
40 q.pop();
41 for (R i=firs[beg];i;i=g[i].nex)
42 {
43 j=g[i].too;
44 if(g[i].cap<=g[i].flow) continue;
45 if(vis[j]) continue;
46 vis[j]=true;
47 d[j]=d[beg]+1;
48 q.push(j);
49 }
50 }
51 return vis[t];
52 }
53
54 int dfs (int x,int minf)
55 {
56 if(x==t||minf==0) return minf;
57 int j,flow=0,f;
58 for (R i=firs[x];i;i=g[i].nex)
59 {
60 j=g[i].too;
61 if(d[j]!=d[x]+1) continue;
62 f=dfs(j,min(minf,g[i].cap-g[i].flow));
63 if(f<=0) continue;
64 g[i].flow+=f;
65 g[i^1].flow-=f;
66 minf-=f;
67 flow+=f;
68 if(minf==0) break;
69 }
70 return flow;
71 }
72
73 int maxflow()
74 {
75 int flow=0;
76 while (bfs())
77 flow+=dfs(s,inf);
78 return flow;
79 }
80
81 int main()
82 {
83 scanf("%d%d",&m,&n);
84 s=0,t=n+m+1;
85 for (R i=1;i<=m;++i)
86 {
87 scanf("%d",&co);
88 add(s,i+n,co);
89 add(i+n,s,0);
90 ans+=co;
91 char tools[10000];
92 memset(tools,0,sizeof tools);
93 cin.getline(tools,10000);
94 int ulen=0,tool;
95 while (sscanf(tools+ulen,"%d",&tool)==1)
96 {
97 add(i+n,tool,inf);
98 add(tool,i+n,0);
99 if (tool==0)
100 ulen++;
101 else
102 {
103 while (tool)
104 {
105 tool/=10;
106 ulen++;
107 }
108 }
109 ulen++;
110 }
111 }
112 for (R i=1;i<=n;++i)
113 {
114 scanf("%d",&x);
115 add(i,t,x);
116 add(t,i,0);
117 }
118 int mf=maxflow();
119 for (R i=n+1;i<=n+m;++i)
120 if(vis[i]) sta[++Top]=i-n;
121 for (R i=1;i<Top;++i)
122 printf("%d ",sta[i]);
123 printf("%d",sta[Top]);
124 printf("\n");
125 Top=0;
126 for (R i=1;i<=n;++i)
127 if(vis[i]) sta[++Top]=i;
128 for (R i=1;i<Top;++i)
129 printf("%d ",sta[i]);
130 printf("%d",sta[Top]);
131 printf("\n");
132 printf("%d",ans-mf);
133 return 0;
134 }
4.电影迷:https://www.luogu.org/problemnew/show/P3872
题意概述:看电影,有的电影可以带来正的体验,有的可以带来负的,有的电影之间有联系,如果看了一部而没看另一部就会有负的体验值,求最高体验值.
直觉上好像只需要选收益为正的?然而不是,有时候可能你明明知道这个电影很难看,但是因为看过好看的上一部就特别想看一下,这时如果不看会带来更差的体验,所以贪心是不行的,考虑网络流.
答案初始化为所有正权值的和,网络流求出的是在这个基础上的最小损失。首先设立源汇,S表示要看,T表示不看。将正体验的电影与源点连容量为权值的边,负体验的与汇点连容量为权值相反数的边。对于依赖关系,从依赖的一方向被依赖的连边,容量为依赖值。求最小割,再用刚刚求出的答案减一下就是最终答案了。解释一下:对于一组体验值一正一负的电影,想分离ST有三种方法:割断S连出的边->放弃正体验电影,割断两者之间的边->只看正体验的,损失依赖值,割断T的边->两个都看,加上负体验(减去负体验的相反数)。
1 # include <cstdio> 2 # include <iostream> 3 # include <cstring> 4 # include <queue> 5 # define R register int 6 # define inf 1000000000 7 8 using namespace std; 9 10 const int maxn=105; 11 int n,m,s,t,h=1,ans,x,y,z; 12 int a[maxn],firs[maxn],d[maxn],vis[maxn]; 13 queue <int> q; 14 struct edge 15 { 16 int too,nex,flow,cap; 17 }g[500005]; 18 19 void add (int x,int y,int co) 20 { 21 g[++h].nex=firs[x]; 22 firs[x]=h; 23 g[h].too=y; 24 g[h].cap=co; 25 g[++h].nex=firs[y]; 26 firs[y]=h; 27 g[h].too=x; 28 } 29 30 bool bfs () 31 { 32 memset(vis,0,sizeof(vis)); 33 vis[s]=1; 34 q.push(s); 35 int beg,j; 36 while(q.size()) 37 { 38 beg=q.front(); 39 q.pop(); 40 for (R i=firs[beg];i;i=g[i].nex) 41 { 42 j=g[i].too; 43 if(vis[j]) continue; 44 if(g[i].cap<=g[i].flow) continue; 45 d[j]=d[beg]+1; 46 vis[j]=true; 47 q.push(j); 48 } 49 } 50 return vis[t]; 51 } 52 53 int dfs (int s,int minf) 54 { 55 if(s==t||minf==0) return minf; 56 int flow=0,f,j; 57 for (R i=firs[s];i;i=g[i].nex) 58 { 59 j=g[i].too; 60 if(d[j]!=d[s]+1) continue; 61 f=dfs(j,min(minf,g[i].cap-g[i].flow)); 62 if(f<=0) continue; 63 minf-=f; 64 flow+=f; 65 g[i].flow+=f; 66 g[i^1].flow-=f; 67 if(minf==0) break; 68 } 69 return flow; 70 } 71 72 int maxflow() 73 { 74 int ans=0; 75 while(bfs()) ans+=dfs(s,inf); 76 return ans; 77 } 78 79 int main() 80 { 81 scanf("%d%d",&n,&m); 82 s=0,t=n+1; 83 for (R i=1;i<=n;++i) 84 { 85 scanf("%d",&a[i]); 86 if(a[i]>0) add(s,i,a[i]),ans+=a[i]; 87 else add(i,t,-a[i]); 88 } 89 for (R i=1;i<=m;++i) 90 { 91 scanf("%d%d%d",&x,&y,&z); 92 add(x,y,z); 93 } 94 printf("%d",max(ans-maxflow(),0)); 95 return 0; 96 }
5.蜥蜴:https://www.lydsy.com/JudgeOnline/problem.php?id=1066
题意概述:略。
如果点有最多经过次数,可以将它拆成一个出点一个入点,中间连上容量为最多经过次数的边。
1 // luogu-judger-enable-o2 2 # include <cstdio> 3 # include <iostream> 4 # include <cstring> 5 # include <queue> 6 # include <string> 7 # include <queue> 8 # define inf 10000009 9 # define R register int 10 11 using namespace std; 12 13 const int maxn=41; 14 int ans,n,m,D,s,t,cnt,h=1; 15 char st[100]; 16 int id[maxn][maxn][2],a[maxn][maxn],firs[maxn*maxn],d[maxn*maxn],vis[maxn*maxn],cur[maxn*maxn]; 17 struct edge 18 { 19 int too,nex,cap; 20 }g[maxn*maxn*200]; 21 queue <int> q; 22 int ab (int x) { if(x<0) return -x; return x; } 23 int dis (int a,int b,int c,int d) { return ab(a-c)+ab(b-d); } 24 25 void add (int x,int y,int cap) 26 { 27 g[++h].nex=firs[x]; 28 g[h].too=y; 29 firs[x]=h; 30 g[h].cap=cap; 31 g[++h].nex=firs[y]; 32 g[h].too=x; 33 firs[y]=h; 34 g[h].cap=0; 35 } 36 37 bool bfs() 38 { 39 memset(vis,0,sizeof(vis)); 40 q.push(s); 41 int beg,j; 42 vis[s]=true; 43 while(q.size()) 44 { 45 beg=q.front(); 46 q.pop(); 47 for (R i=firs[beg];i;i=g[i].nex) 48 { 49 j=g[i].too; 50 if(vis[j]) continue; 51 if(g[i].cap<=0) continue; 52 d[j]=d[beg]+1; 53 vis[j]=true; 54 q.push(j); 55 } 56 } 57 return vis[t]; 58 } 59 60 int dfs (int x,int minf) 61 { 62 if(minf==0||x==t) return minf; 63 int flow=0,f,j; 64 for (int& i=cur[x];i;i=g[i].nex) 65 { 66 j=g[i].too; 67 if(d[j]!=d[x]+1) continue; 68 f=dfs(j,min(minf,g[i].cap)); 69 if(f<=0) continue; 70 g[i].cap-=f; 71 g[i^1].cap+=f; 72 flow+=f; 73 minf-=f; 74 if(minf==0) break; 75 } 76 return flow; 77 } 78 79 void init() { for (R i=0;i<=cnt;++i) cur[i]=firs[i]; } 80 81 int dinic() 82 { 83 int ans=0; 84 while(bfs()) 85 { 86 init(); 87 ans+=dfs(s,inf); 88 } 89 return ans; 90 } 91 92 int main() 93 { 94 scanf("%d%d%d",&n,&m,&D); 95 s=0; 96 for (R i=1;i<=n;++i) 97 for (R j=1;j<=m;++j) 98 scanf("%1d",&a[i][j]),id[i][j][0]=++cnt; 99 for (R i=1;i<=n;++i) 100 for (R j=1;j<=m;++j) id[i][j][1]=++cnt; 101 t=++cnt; 102 for (R i=1;i<=n;++i) 103 for (R j=1;j<=m;++j) 104 { 105 if(a[i][j]==0) continue; 106 add(id[i][j][0],id[i][j][1],a[i][j]); 107 if(i<=D||j<=D||n-i+1<=D||m-j+1<=D) 108 add(id[i][j][1],t,inf); 109 } 110 for (R i=1;i<=n;++i) 111 { 112 scanf("%s",st+1); 113 for (R j=1;j<=m;++j) 114 if(st[j]=='L') add(s,id[i][j][0],1),ans++; 115 } 116 for (R i=1;i<=n;++i) 117 for (R j=1;j<=m;++j) 118 { 119 if(a[i][j]==0) continue; 120 for (R k=1;k<=n;++k) 121 for (R z=1;z<=m;++z) 122 { 123 if(i==k&&j==z) continue; 124 if(dis(i,j,k,z)>D) continue; 125 if(a[k][z]==0) continue; 126 add(id[i][j][1],id[k][z][0],inf); 127 } 128 } 129 printf("%d",ans-dinic()); 130 return 0; 131 }
6.奶牛的电信:https://www.luogu.org/problemnew/show/P1345
题意概述:求一张图上至少坏掉多少点才能使得ST不连通。
和上一道题相仿,拆点。因为这次边不能断,所以原图中的边在新图中权值设为inf。
1 # include <cstdio> 2 # include <iostream> 3 # include <cstring> 4 # include <queue> 5 # define inf 70000000 6 7 inline int min (int a,int b) { if(a<b) return a; return b; } 8 const int maxn=105; 9 const int maxm=605; 10 int n,m,s,t,x,y,h=1; 11 int firs[maxn<<1],d[maxn<<1]; 12 bool vis[maxn<<1]; 13 std::queue <int> q; 14 struct edge 15 { 16 int too,nex,flow,cap; 17 }g[(maxm<<3)+maxn]; 18 19 void add (int x,int y,int cap) 20 { 21 g[++h].cap=cap; 22 g[h].too=y; 23 g[h].nex=firs[x]; 24 firs[x]=h; 25 } 26 27 bool bfs() 28 { 29 std::memset(vis,0,sizeof(vis)); 30 while (q.size()) q.pop(); 31 q.push(s); 32 vis[s]=true; 33 d[s]=0; 34 int beg,j; 35 while (q.size()) 36 { 37 beg=q.front(); 38 q.pop(); 39 for (int i=firs[beg];i;i=g[i].nex) 40 { 41 j=g[i].too; 42 if(vis[j]) continue; 43 if(g[i].cap<=g[i].flow) continue; 44 d[j]=d[beg]+1; 45 vis[j]=true; 46 q.push(j); 47 } 48 } 49 return vis[t]; 50 } 51 52 int dfs (int x,int minf) 53 { 54 if(minf==0||x==t) return minf; 55 int j,flow=0,f; 56 for (int i=firs[x];i;i=g[i].nex) 57 { 58 j=g[i].too; 59 if(d[j]!=d[x]+1) continue; 60 f=dfs(j,min(minf,g[i].cap-g[i].flow)); 61 if(f<=0) continue; 62 g[i].flow+=f; 63 g[i^1].flow-=f; 64 minf-=f; 65 flow+=f; 66 if(minf==0) break; 67 } 68 return flow; 69 } 70 71 int maxflow() 72 { 73 int ans=0; 74 while (bfs()) 75 ans+=dfs(s,inf); 76 return ans; 77 } 78 79 int main() 80 { 81 scanf("%d%d%d%d",&n,&m,&s,&t); 82 for (int i=1;i<=m;++i) 83 { 84 scanf("%d%d",&x,&y); 85 add(x+n,y,inf); 86 add(y,x+n,0); 87 add(y+n,x,inf); 88 add(x,y+n,0); 89 } 90 for (int i=1;i<=n;++i) 91 if(i!=s&&i!=t) 92 { 93 add(i,i+n,1); 94 add(i+n,i,0); 95 } 96 add(s,s+n,inf); 97 add(s+n,s,0); 98 add(t,t+n,inf); 99 add(t+n,t,0); 100 printf("%d",maxflow()); 101 return 0; 102 }
7.假期的宿舍:https://www.lydsy.com/JudgeOnline/problem.php?id=1433
题意概述:二分图最大匹配。
设立源点S,汇点T,S向一部连边,另一部向T连边,对于可以匹配的关系,就从S一侧向T一侧连边。注意:显然本校生可以睡自己的床。
1 # include <cstdio> 2 # include <iostream> 3 # include <queue> 4 # include <cstring> 5 # define R register int 6 # define inf 1e8 7 8 int T; 9 int n,l,r,x,h,s=0,t; 10 int a[550],firs[550],d[550]; 11 std::queue <int> q; 12 bool vis[150]; 13 struct hu 14 { 15 int nex,too,cap,flow; 16 }g[25009]; 17 18 int min(int a,int b) 19 { 20 if(a<b) return a; 21 return b; 22 } 23 24 int dfs(int x,int minf) 25 { 26 if(x==t||minf==0) return minf; 27 int j,flow=0,f; 28 for (R i=firs[x];i;i=g[i].nex) 29 { 30 j=g[i].too; 31 if(d[j]!=d[x]+1) continue; 32 f=dfs(j,min(minf,g[i].cap-g[i].flow)); 33 if(f<=0) continue; 34 g[i].flow+=f; 35 g[i^1].flow-=f; 36 flow+=f; 37 minf-=f; 38 if(minf==0) break; 39 } 40 return flow; 41 } 42 43 bool bfs() 44 { 45 memset(vis,0,sizeof(vis)); 46 vis[s]=true; 47 while (q.size()) q.pop(); 48 q.push(s); 49 int j,beg; 50 while (q.size()) 51 { 52 beg=q.front(); 53 q.pop(); 54 for (R i=firs[beg];i;i=g[i].nex) 55 { 56 j=g[i].too; 57 if(vis[j]) continue; 58 if(g[i].cap<=g[i].flow) continue; 59 vis[j]=true; 60 d[j]=d[beg]+1; 61 q.push(j); 62 } 63 } 64 return vis[t]; 65 } 66 67 int maxflow(int s) 68 { 69 int ans=0; 70 while (bfs()) 71 ans+=dfs(s,inf); 72 return ans; 73 } 74 75 void add(int x,int y,int v) 76 { 77 g[++h].too=y; 78 g[h].nex=firs[x]; 79 firs[x]=h; 80 g[h].cap=v; 81 g[h].flow=0; 82 } 83 84 int main() 85 { 86 scanf("%d",&T); 87 while (T--) 88 { 89 h=1; 90 l=0,r=0; 91 memset(firs,0,sizeof(firs)); 92 scanf("%d",&n); 93 for (int i=1;i<=n;++i) 94 { 95 scanf("%d",&a[i]); 96 if(a[i]==0) a[i]=-1; 97 } 98 for (int i=1;i<=n;++i) 99 { 100 scanf("%d",&x); 101 if(a[i]==1) a[i]-=x; //0:回家; 1:留下 102 } 103 t=n*2+5; 104 for (int i=1;i<=n;++i) 105 if(a[i]!=0) l++,add(s,i,1),add(i,s,0); 106 for (int i=1;i<=n;++i) 107 if(a[i]>=0) add(i+n,t,1),add(t,i+n,0); 108 for (int i=1;i<=n;++i) 109 for (int j=1;j<=n;++j) 110 { 111 scanf("%d",&x); 112 if(x||i==j) 113 { 114 //i,j可以互相睡对方的床 115 if(a[i]>=0&&a[j]!=0) 116 add(j,i+n,1),add(i+n,j,0); 117 if(a[j]>=0&&a[i]!=0) 118 add(i,j+n,1),add(j+n,i,0); 119 } 120 } 121 if(maxflow(s)<l) printf("T_T\n"); 122 else printf("^_^\n"); 123 } 124 return 0; 125 }
8.分配问题:https://www.luogu.org/problemnew/show/P4014
题意概述:二分图最大带权匹配。
匹配关系上加上费用,跑费用流即可。
1 # include <cstdio> 2 # include <iostream> 3 # include <cstring> 4 # include <queue> 5 # define R register int 6 # define inf 100000000 7 8 using namespace std; 9 10 const int maxn=102; 11 int ans,ans1,ans2,n,h=1,a[maxn][maxn],firs[maxn<<1],d[maxn<<1],pre[maxn<<1]; 12 int in_que[maxn<<1],Fl[maxn<<1],s,t; 13 struct edge 14 { 15 int too,nex,cap,co; 16 }g[maxn*maxn<<1]; 17 queue <int> q; 18 19 void add (int x,int y,int cap,int co) 20 { 21 g[++h].too=y; 22 g[h].nex=firs[x]; 23 firs[x]=h; 24 g[h].cap=cap; 25 g[h].co=co; 26 g[++h].too=x; 27 g[h].nex=firs[y]; 28 firs[y]=h; 29 g[h].cap=0; 30 g[h].co=-co; 31 } 32 33 bool bfs (int col) 34 { 35 if(col) memset(d,-0x3f,sizeof(d)); 36 else memset(d,0x3f,sizeof(d)); 37 q.push(s); 38 int f=d[s]; 39 d[s]=0; 40 Fl[s]=inf; 41 int beg,j; 42 while(q.size()) 43 { 44 beg=q.front(); 45 q.pop(); 46 in_que[beg]=0; 47 for (R i=firs[beg];i;i=g[i].nex) 48 { 49 j=g[i].too; 50 if(g[i].cap<=0) continue; 51 if(col&&d[j]>=d[beg]+g[i].co) continue; 52 if(col==0&&d[j]<=d[beg]+g[i].co) continue; 53 d[j]=d[beg]+g[i].co; 54 pre[j]=i; 55 Fl[j]=min(Fl[beg],g[i].cap); 56 if(!in_que[j]) in_que[j]=true,q.push(j); 57 } 58 } 59 return (d[t]!=f); 60 } 61 62 void dfs () 63 { 64 int x=t,i; 65 while(x!=s) 66 { 67 i=pre[x]; 68 g[i].cap-=Fl[t]; 69 g[i^1].cap+=Fl[t]; 70 x=g[i^1].too; 71 } 72 ans+=d[t]*Fl[t]; 73 } 74 75 int main() 76 { 77 scanf("%d",&n); 78 for (R i=1;i<=n;++i) 79 for (R j=1;j<=n;++j) 80 scanf("%d",&a[i][j]); 81 t=2*n+1; 82 for (R i=1;i<=n;++i) 83 add(s,i,1,0),add(i+n,t,1,0); 84 for (R i=1;i<=n;++i) 85 for (R j=1;j<=n;++j) 86 add(i,n+j,1,a[i][j]); 87 while(bfs(0)) dfs(); 88 ans1=ans; 89 ans=0; 90 t=2*n+1; 91 h=1,memset(firs,0,sizeof(firs)); 92 for (R i=1;i<=n;++i) 93 add(s,i,1,0),add(i+n,t,1,0); 94 for (R i=1;i<=n;++i) 95 for (R j=1;j<=n;++j) 96 add(i,n+j,1,a[i][j]); 97 while(bfs(1)) dfs(); 98 ans2=ans; 99 printf("%d\n%d",ans1,ans2); 100 return 0; 101 }
9.试题库问题:https://www.luogu.org/problemnew/show/P2763
题意概述:题库中有很多的题目,每道题有多个属性,要求抽出M道题,每道题仅表现一种属性,使得表现每种属性的试题数量恰好为给定值。
这道题的题面有一点难理解,关键是要明白每道题一旦被选中,就只能表现一种属性,而不是所有属性都表现。
对于每种属性设置一个点,从源点向它连边,容量为需求量。对于每道题的每种属性,由属性向题目连边,容量为一。因为每道题只能贡献一种属性,所以由题目再向汇点连边。求一遍最大流,输出方案时找到每个属性连向的题目输出即可。
1 # include <cstdio> 2 # include <iostream> 3 # include <cstring> 4 # include <queue> 5 # define R register int 6 # define inf 1000000009 7 8 using namespace std; 9 10 const int maxn=1050; 11 int n,k,m,firs[maxn],cur[maxn],d[maxn],h=1,s,t,S,x,y; 12 struct edge { int too,nex,cap,flow; } g[50004]; 13 queue <int> q; 14 15 void add (int x,int y,int cap) 16 { 17 g[++h].nex=firs[x]; 18 g[h].too=y; 19 firs[x]=h; 20 g[h].cap=cap; 21 g[++h].nex=firs[y]; 22 g[h].too=x; 23 firs[y]=h; 24 g[h].cap=0; 25 } 26 27 bool bfs() 28 { 29 memset(d,0,sizeof(d)); 30 d[s]=1; 31 q.push(s); 32 int beg,j; 33 while(q.size()) 34 { 35 beg=q.front(); 36 q.pop(); 37 for (R i=firs[beg];i;i=g[i].nex) 38 { 39 j=g[i].too; 40 if(d[j]) continue; 41 if(g[i].cap<=g[i].flow) continue; 42 d[j]=d[beg]+1; 43 q.push(j); 44 } 45 } 46 return d[t]; 47 } 48 49 int dfs (int x,int minf) 50 { 51 if(minf==0||x==t) return minf; 52 int flow=0,f,j; 53 for (int& i=cur[x];i;i=g[i].nex) 54 { 55 j=g[i].too; 56 if(d[j]!=d[x]+1) continue; 57 f=dfs(j,min(minf,g[i].cap-g[i].flow)); 58 if(f<=0) continue; 59 g[i].flow+=f; 60 g[i^1].flow-=f; 61 flow+=f; 62 minf-=f; 63 if(minf==0) break; 64 } 65 return flow; 66 } 67 68 void init() { for (R i=0;i<=t;++i) cur[i]=firs[i]; } 69 70 int dinic() 71 { 72 int ans=0; 73 while(bfs()) 74 { 75 init(); 76 ans+=dfs(s,inf); 77 } 78 return ans; 79 } 80 81 int main() 82 { 83 scanf("%d%d",&k,&n); 84 t=k+n+1; 85 for (R i=1;i<=k;++i) 86 scanf("%d",&x),S+=x,add(s,i,x); 87 for (R i=1;i<=n;++i) 88 { 89 add(i+k,t,1); 90 scanf("%d",&x); 91 for (R j=1;j<=x;++j) 92 { 93 scanf("%d",&y); 94 add(y,i+k,1); 95 } 96 } 97 if(dinic()!=S) puts("No Solution!"); 98 else 99 { 100 for (R i=1;i<=k;++i) 101 { 102 printf("%d:",i); 103 for (R j=firs[i];j;j=g[j].nex) 104 { 105 if(j%2) continue; 106 if(g[j].cap==g[j].flow) printf(" %d",g[j].too-k); 107 } 108 printf("\n"); 109 } 110 } 111 return 0; 112 }
10.最小路径覆盖问题:https://www.luogu.org/problemnew/show/P2764
题意概述:DAG的最小点覆盖(找出最少的路径,使得每个点被经过恰好一次)。
如果直接按照题目的意思去做,就会非常麻烦,但是好像也不是不能做...就是每个点拆成出点和入点,中间连一条上下界均为一的边,每个入点和 S1 连边,出点和 T 连边,图中原有的边就按题意连一连,再从S向S'连边,对于这张图求一个带源汇上下界最小流即为答案。这样做的好处是非常好想“智商不够,高级算法来凑”。
简单一点的做法:首先要逆向思维,认为是每个点都独立成为路径,答案为N,再考虑哪些路径可以首尾相连变成一条。依旧拆点,对于原图中的每条边,由起点拆出的左点连向终点拆出的右点,形象化的理解就是某条路径到达它后没有结束,而是接着又连上了以终点为开头的一条新路径,做二分图最大匹配,即为“可以省掉的路径数目”。
1 # include <cstdio> 2 # include <iostream> 3 # include <cstring> 4 # include <queue> 5 # define R register int 6 # define inf 1000000009 7 8 using namespace std; 9 10 const int maxn=302; 11 int ans,n,m,firs[maxn],s,t,h=1,x,y,vis[maxn],st[maxn],d[maxn],cur[maxn],r[maxn],Tp,f[maxn]; 12 struct edge 13 { 14 int too,nex,cap; 15 }g[20004]; 16 queue <int> q; 17 18 inline int read() 19 { 20 R x=0; 21 char c=getchar(); 22 while (!isdigit(c)) c=getchar(); 23 while (isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=getchar(); 24 return x; 25 } 26 27 inline void add (int x,int y,int cap) 28 { 29 g[++h].nex=firs[x]; 30 g[h].too=y; 31 g[h].cap=cap; 32 firs[x]=h; 33 g[++h].nex=firs[y]; 34 g[h].too=x; 35 g[h].cap=0; 36 firs[y]=h; 37 } 38 39 int dfs (int x,int minf) 40 { 41 if(x==t||minf==0) return minf; 42 int j,f,flow=0; 43 for (R &i=cur[x];i;i=g[i].nex) 44 { 45 j=g[i].too; 46 if(d[j]!=d[x]+1) continue; 47 f=dfs(j,min(minf,g[i].cap)); 48 if(f<=0) continue; 49 g[i].cap-=f; 50 g[i^1].cap+=f; 51 flow+=f; 52 minf-=f; 53 if(minf==0) break; 54 } 55 return flow; 56 } 57 58 inline bool bfs () 59 { 60 int j,beg; 61 memset(vis,0,sizeof(vis)); 62 d[s]=1; 63 q.push(s); 64 vis[s]=true; 65 while (q.size()) 66 { 67 beg=q.front(); 68 q.pop(); 69 for (R i=firs[beg];i;i=g[i].nex) 70 { 71 if(g[i].cap<=0) continue; 72 j=g[i].too; 73 if(vis[j]) continue; 74 vis[j]=true; 75 d[j]=d[beg]+1; 76 q.push(j); 77 } 78 } 79 return vis[t]; 80 } 81 82 inline int dinic () 83 { 84 int ans=0; 85 while (bfs()) 86 { 87 for (R i=0;i<=t;++i) cur[i]=firs[i]; 88 ans+=dfs(s,inf); 89 } 90 return ans; 91 } 92 93 int main() 94 { 95 n=read(),m=read(); 96 t=2*n+1; 97 for (R i=1;i<=n;++i) 98 add(s,i,1),add(i+n,t,1); 99 for (R i=1;i<=m;++i) 100 { 101 x=read(),y=read(); 102 add(x,y+n,1); 103 } 104 ans=n-dinic(); 105 for (R i=1;i<=n;++i) f[i]=i; 106 for (R i=1;i<=n;++i) 107 for (R k=firs[i];k;k=g[k].nex) 108 { 109 if(k&1) continue; 110 if(g[k].cap==0) 111 { 112 f[ g[k].too-n ]=i; 113 r[ i ]++; 114 break; 115 } 116 } 117 for (R i=1;i<=n;++i) if(!r[i]) q.push(i); 118 int beg=0; 119 while(q.size()) 120 { 121 beg=q.front(); 122 q.pop(); 123 Tp=0; 124 while (beg!=f[beg]) 125 { 126 st[ ++Tp ]=beg; 127 beg=f[beg]; 128 } 129 st[++Tp]=beg; 130 for (R i=Tp;i>=1;--i) printf("%d ",st[i]); 131 printf("\n"); 132 } 133 printf("%d",ans); 134 return 0; 135 }
11.无源汇上下界可行流:https://loj.ac/problem/115
循环往复,无始无终。
这个算法还是有一点难理解的,虽然上面讲过了,但是过于形式化,所以这里再写一些。
思路是先让所有的下界都满足,再用处于上下界之间的自由流来调整到合法。先将下界所需要的流都流上,并依此处理出每个点开始就有的流量,设为$a_i$。连边时流量设为 $max-min$,此时发现不满足流量平衡,而且网络流也没有什么“初始时就有的流”这么一说,所以让我们思考一下。如果一个点的初始流量大于0,也就是说对于仅保留自由流量边的那张新图,它的流出应该等于流入加 $a_i$ ,怎么实现这一要求呢?在网络流中,唯二可以不满足流量平衡的就是源和汇,所以可以加上一个附加源,向 $i$ 点连流量为$a_i$ 的边,附加汇同理。此时再跑一边最大流,如果所有的附加边都满流了,说明所有不合法的流都解决了,也就是一个可行流。
1 # include <cstdio> 2 # include <iostream> 3 # include <string> 4 # include <cstring> 5 # include <queue> 6 # define R register int 7 # define inf 1000000009 8 9 using namespace std; 10 11 const int maxn=202; 12 int n,m,firs[maxn],h=1,a[maxn],d[maxn],s,t,x,y,mas,mis,flow[10405],cur[maxn],S; 13 struct edge { int too,nex,cap,flow; }g[25000]; 14 queue <int> q; 15 16 inline int read() 17 { 18 R x=0; 19 char c=getchar(); 20 while (!isdigit(c)) c=getchar(); 21 while (isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=getchar(); 22 return x; 23 } 24 25 void add (int x,int y,int cap) 26 { 27 g[++h].nex=firs[x]; 28 g[h].too=y; 29 firs[x]=h; 30 g[h].cap=cap; 31 g[++h].nex=firs[y]; 32 g[h].too=x; 33 firs[y]=h; 34 g[h].cap=0; 35 } 36 37 bool bfs() 38 { 39 memset(d,0,sizeof(d)); 40 d[s]=1; 41 q.push(s); 42 int beg,j; 43 while(q.size()) 44 { 45 beg=q.front(); 46 q.pop(); 47 for (R i=firs[beg];i;i=g[i].nex) 48 { 49 j=g[i].too; 50 if(d[j]) continue; 51 if(g[i].cap<=g[i].flow) continue; 52 d[j]=d[beg]+1; 53 q.push(j); 54 } 55 } 56 return d[t]; 57 } 58 59 int dfs (int x,int minf) 60 { 61 if(minf==0||x==t) return minf; 62 int flow=0,f,j; 63 for (int& i=cur[x];i;i=g[i].nex) 64 { 65 j=g[i].too; 66 if(d[j]!=d[x]+1) continue; 67 f=dfs(j,min(minf,g[i].cap-g[i].flow)); 68 if(f<=0) continue; 69 g[i].flow+=f; 70 g[i^1].flow-=f; 71 flow+=f; 72 minf-=f; 73 if(minf==0) break; 74 } 75 return flow; 76 } 77 78 void init() { for (R i=0;i<=t;++i) cur[i]=firs[i]; } 79 80 int dinic() 81 { 82 int ans=0; 83 while(bfs()) 84 { 85 init(); 86 ans+=dfs(s,inf); 87 } 88 return ans; 89 } 90 91 int main() 92 { 93 n=read(),m=read(); 94 t=n+1; 95 for (R i=1;i<=m;++i) 96 { 97 x=read(),y=read(),mis=read(),mas=read(); 98 flow[i]=mis; 99 add(x,y,mas-mis); 100 a[y]+=mis,a[x]-=mis; 101 } 102 for (R i=1;i<=n;++i) 103 if(a[i]>0) add(s,i,a[i]),S+=a[i]; 104 else add(i,t,-a[i]); 105 if(dinic()!=S) puts("NO"); 106 else 107 { 108 puts("YES"); 109 for (R i=1;i<=m;++i) 110 printf("%d\n",flow[i]+g[i*2].flow); 111 } 112 return 0; 113 }
---shzr