网络流专题

T1:

时空隧道

还是翻译一下...(CtrlC+CtrlV)...

Seal

【题目背景】 NOI2030 前夜,作为出题人的 Z 君正在颓隔膜。这个隔膜是这样的:在上古 秘境中,勇者们发现了封印古代恶魔的地方。不幸的是,这里的封印已经濒临崩 溃,恶魔们即将苏醒。勇者们决定使用魔法水晶加固封印,让恶魔们再次沉睡。

【题目描述】 封印恶魔的地方可以看作是一个 n*m 的矩形,包含了 n*m 个祭坛,并且其 中有一些祭坛已经损坏了。如果 i+j 为偶数,那么第 i 行第 j 列的祭坛只要没有损 坏,就一定会封印有一个恶魔。 其他的没有损坏的祭坛可以用来放置魔法水晶,但是一个祭坛上只能放置一 个魔法水晶,并且一个魔法水晶只能向一个与它相邻的祭坛输送魔力,从而加固 封印。 对于一个恶魔来说,如果与它相邻的两个成直角的水晶同时向它所在的祭坛 输送魔力的话,它就会被再次封印。 现在 Z 君想知道他最多可以封印多少恶魔?

【输入格式】 第一行两个整数 n,m,含义见题目描述。 接下来 n 行,每行一个长度为 m 的字符串,只包含’X’和’.’。如果第 i 行第 j 个字符为’X’,则意味着第 i 行第 j 列的祭坛已经损坏。

【输出格式】 一行一个整数表示答案。

【样例输入】

3 3

【样例输出】

2

【数据规模与约定】

对于 40%的数据,m≤10。

对于 100%的数据,1≤n,m≤50,0≤K≤n*m。

分析:

考试的时候我的想法是这样滴:

因为每个恶魔四周都有四个点可以向它连边,而我们要求的是选择上下中的一个和左右中的一个向他连边...所以我们需要把这四个点分类...

因为发现左右的点和上下的点行编号的奇偶性永远不同,所以我们把水晶点按照行编号分为两类,S向奇数行编号的点连容量为1的边,奇数行编号的点向偶数行编号的点连容量为1,然后偶数行编号的点向恶魔连容量为1的边...我觉得可对了...但是我造了了一个3*3的数据卡了自己...

3 3

X..

.XX

XXX

ans=0,但是我跑出来是1TAT...

为什么不用我说了吧...

怎么解决...我思考了好久...还是不会...然后听完正解发现我就是个智障...

我们只需要把奇数行编号的点向恶魔连边然后恶魔向偶数行编号的点连边就好了...

这就是一种解决限制的方法...受教受教...

代码:

 1 #include<algorithm>
 2 #include<iostream>
 3 #include<cstring>
 4 #include<cstdio>
 5 //by NeighThorn
 6 #define inf 0x3f3f3f3f
 7 using namespace std;
 8 //gao shan yang zhi,jing hang xing zhi
 9 
10 const int N=50+5,maxn=10000+5,maxm=2000000+5;
11 
12 int n,m,S,T,cnt,hd[maxn],fl[maxm],to[maxm],nxt[maxm],pos[maxn];
13 
14 int mv[4][2]={0,1,0,-1,1,0,-1,0};
15 
16 char mp[N][N];
17 
18 inline bool bfs(void){
19     memset(pos,-1,sizeof(pos));
20     int head=0,tail=0,q[maxn];
21     q[0]=S,pos[S]=0;
22     while(head<=tail){
23         int top=q[head++];
24         for(int i=hd[top];i!=-1;i=nxt[i])
25             if(pos[to[i]]==-1&&fl[i])
26                 pos[to[i]]=pos[top]+1,q[++tail]=to[i];
27     }
28     return pos[T]!=-1;
29 }
30 
31 inline int find(int v,int f){
32     if(v==T)
33         return f;
34     int res=0,t;
35     for(int i=hd[v];i!=-1&&f>res;i=nxt[i])
36         if(pos[to[i]]==pos[v]+1&&fl[i])
37             t=find(to[i],min(f-res,fl[i])),res+=t,fl[i]-=t,fl[i^1]+=t;
38     if(!res)
39         pos[v]=-1;
40     return res;
41 }
42 
43 inline int dinic(void){
44     int res=0,t;
45     while(bfs())
46         while(t=find(S,inf))
47             res+=t;
48     return res;
49 }
50 
51 inline void add(int s,int x,int y){
52     fl[cnt]=s;to[cnt]=y;nxt[cnt]=hd[x];hd[x]=cnt++;
53     fl[cnt]=0;to[cnt]=x;nxt[cnt]=hd[y];hd[y]=cnt++;
54 }
55 
56 signed main(void){
57     freopen("Seal.in","r",stdin);
58     freopen("Seal.out","w",stdout);
59     scanf("%d%d",&n,&m);
60     S=0,T=n*m*2+1;cnt=0;
61     memset(hd,-1,sizeof(hd));
62     for(int i=1;i<=n;i++)
63         scanf("%s",mp[i]+1);
64     for(int i=1;i<=n;i++)
65         for(int j=1;j<=m;j++){
66             if((((i+j)&1)==0)&&mp[i][j]!='X'){
67                 add(1,(i-1)*m+j,(i-1)*m+j+n*m);
68                 for(int t=0;t<4;t++){
69                     int x=i+mv[t][0],y=j+mv[t][1];
70                     if(x>=1&&x<=n&&y>=1&&y<=m&&mp[i][j]!='X'){
71                         if(x&1)
72                             add(1,(x-1)*m+y,(i-1)*m+j);
73                         else
74                             add(1,(i-1)*m+j+n*m,(x-1)*m+y);
75                     }   
76                 }
77             }
78             else if(((i+j)&1)&&mp[i][j]!='X'){
79                 if(i&1)
80                     add(1,S,(i-1)*m+j);
81                 else
82                     add(1,(i-1)*m+j,T);
83             }
84         }
85     printf("%d
",dinic());
86     return 0;   
87 }//Cap ou pas cap. Cap.
View Code

T2:

时空隧道

Repair

【题目背景】

NOI2030 的考场上,开考 5 分钟后,出题人 Z 君突然发现由于昨晚颓隔膜, 他的数据造错了。Z 君在造数据之前生成了一个网络流的可行流,但这个可行流 出了一些错误,有一些边的流量超出了容量上限,有一些点不满足流量平衡。现 在,Z 君想用最少的时间改正这些错误。

【题目描述】

给出一张 n 个点、m 条边的有向图,每条边有一个流量上限 c 和一个当前流 量 f。源点是 1,汇点是 n。你可以对每条边的 c 和 f 进行修改,但只能改成非负 整数,修改花费的时间等于改动后的数值与原来的数值之差的绝对值。请问使得 每条边的 0≤f≤c,且除源汇点以外的点流量平衡的最少用时。

【输入格式】

第一行两个整数 n,m。 接下来 m 行,每行四个整数 x,y,c,f,表示有一条从 x 到 y 的容量上限为 c, 当前流量为 f 的边。保证没有自环,可能有重边。

【输出格式】

一行一个整数表示最少时间。

【样例输入】

4 4

1 2 2 3

1 3 2 5

2 4 2 2

3 4 2 4

【样例输出】

6

【数据规模与约定】

对于 100%的数据,1≤n≤500,1≤m≤5,000,0≤c,f≤10,000。

分析:

类似于上下界网络流的做法...我们还是采用源汇补充流的做法...

定义最终流量为l,设x=f-c

No.1 f>c

  1、l>f>c

    ans=l-f+l-c=x+2l-2f

  2、f>l>c

    ans=f-l+l-c=x

  3、f>c>l

    ans=f-l=x-l+c

  我们可以看到每种情况的ans化简之后都含有x=f-c,所以我们直接在ans加上f-c,然后建边...

  需要建一条从x到y的容量为+∞费用为2的边(就是增大流量增大容量),还有从y到x的容量为f-c费用为0的边(减小流量直到符合容量),还有从y到x容量为c费用为1的边(减少流量)...(这些应该很好理解吧...)

No.2 f<=c

  和上面差不多,依旧分类讨论一下...(我懒癌晚期...就不写了...)

  然后add(y,x,f,1),add(x,y,c-f,1),add(x,y,inf,2)...

现在我们可以满足所有的边都符合要求...那么考虑点是否流量守恒...

如果in[i]>out[i]那么这个点需要流出in[i]-out[i]的流,那么就从S到i连一条容量为in[i]-out[i]的边,如果out[i]>in[i]那么这个点需要流入out[i]-in[i]的流,那么就从这个点向T连一条容量为out[i]-in[i]的边,(包括st),但是现在我们要求st不能流量守恒,所以我们要从t向s连一条容量为+∞的边,这样就把流向t的流都流回了s...

然后跑最小费用最大流就好了...

代码:

 1 #include<algorithm>
 2 #include<iostream>
 3 #include<cstring>
 4 #include<cstdio>
 5 #include<queue>
 6 //by NeighThorn
 7 #define inf 0x3f3f3f3f
 8 using namespace std;
 9 //nan you qiao mu,bu ke xiu si
10 
11 const int maxn=100+5,maxm=10000+5;
12 
13 int n,m,S,T,ans,cnt,w[maxm],hd[maxn],fl[maxm],to[maxm],nxt[maxm],pos[maxn],dif[maxn],dis[maxn],Min[maxn],vis[maxn],from[maxn];
14 
15 inline bool spfa(void){
16     memset(dis,inf,sizeof(dis));
17     memset(Min,inf,sizeof(Min));
18     queue<int> q;q.push(S),vis[S]=1,dis[S]=0;
19     while(!q.empty()){
20         int top=q.front();q.pop();vis[top]=0;
21         for(int i=hd[top];i!=-1;i=nxt[i])
22             if(dis[to[i]]>dis[top]+w[i]&&fl[i]){
23                 from[to[i]]=i;
24                 dis[to[i]]=dis[top]+w[i];
25                 Min[to[i]]=min(Min[top],fl[i]);
26                 if(!vis[to[i]])
27                     vis[to[i]]=1,q.push(to[i]);
28             }
29     }
30     return dis[T]!=inf;
31 }
32 
33 inline int find(void){
34     for(int i=T;i!=S;i=to[from[i]^1])
35         fl[from[i]]-=Min[T],fl[from[i]^1]+=Min[T];
36     return Min[T]*dis[T];
37 }
38 
39 inline int dinic(void){
40     int res=0;
41     while(spfa())
42         res+=find();
43     return res;
44 }
45 
46 inline void add(int l,int s,int x,int y){
47     w[cnt]=l;fl[cnt]=s;to[cnt]=y;nxt[cnt]=hd[x];hd[x]=cnt++;
48     w[cnt]=-l;fl[cnt]=0;to[cnt]=x;nxt[cnt]=hd[y];hd[y]=cnt++;
49 }
50 
51 signed main(void){
52     // freopen("in.txt","r",stdin);
53     // freopen("out.txt","w",stdout);
54     cnt=ans=0;
55     memset(hd,-1,sizeof(hd));
56     scanf("%d%d",&n,&m);S=0,T=n+1;
57     for(int i=1,c,f,x,y;i<=m;i++){
58         scanf("%d%d%d%d",&x,&y,&c,&f);dif[x]-=f,dif[y]+=f;
59         if(f>c)
60             ans+=f-c,add(0,f-c,y,x),add(1,c,y,x),add(2,inf,x,y);
61         else
62             add(1,f,y,x),add(1,c-f,x,y),add(2,inf,x,y);
63     }
64     for(int i=1;i<=n;i++){
65         if(dif[i]>0)
66             add(0,dif[i],S,i);
67         else if(dif[i]<0)
68             add(0,-dif[i],i,T);
69     }
70     add(0,inf,n,1);printf("%d
",ans+dinic());
71     return 0;   
72 }//Cap ou pas cap. Cap.
View Code

T3:

先%ZXR...

Flow

【题目背景】

Z 君终于赶在比赛结束前修改完了他的数据,但是他不能确定自己的标程对 于新的数据能否给出正确的答案。于是他只好向你求助,希望你能帮他写一份程 序用来对拍。

【题目描述】

Z 君出的题目是这样的:给出一张 n 个点、m 条边的有向无环图,对于一条 边 e 来说,有一个容量限制 u(e)、一个单位费用 c(e)。源点是 s,汇点是 t。 定义一个流 f 是一个 s-t 流,当且仅当对于每条边 e,f(e)是一个 0~u(e)的实 数,即 0≤f(e)≤u(e),且除源汇两点外每个点的流入流量等于流出流量。 定义流 f 的流量 F(f)和费用 C(f)分别为: F(f)等于源点的流出流量减流入流量; C(f)等于每条边的流量乘以单位费用的和,即Σf(e)*c(e)。 现在定义一个函数 G(f)=C(f)2+(MaxFlow-F(f))2,其中 MaxFlow 等于 max{F(f)}, 即最大流的流量。 要求求出一个 s-t 流 f,使得 G(f)最小,以最简分数 p/q 的形式输出最小的 G(f),保证答案为有理数。

【输入格式】

第一行两个正整数 n,m。 第二行两个正整数 s,t 表示源点和汇点。 接下来 m 行,每行四个正整数 x,y,u,c,表示有一条 x 到 y 的容量上界为 u, 单位费用为 c。

【输出格式】

一行一个分数 p/q 表示最小的 G(f)。

【样例输入】

3 3

1 2

1 2 1 1

1 3 3 1

3 2 3 2

【样例输出】

10/1

【数据规模与约定】

对于 100%的数据,n≤100,m≤1000,u≤100,c≤100。 保证无重边、自环,数据存在梯度。

分析:

再%GZZ...

题解详见YOUSIKI的代码...


By NeighThorn

原文地址:https://www.cnblogs.com/neighthorn/p/6253988.html