[专题总结]网络流初步(1)

总算A穿第一个专题。来屯思路的。

 

 

蜥蜴

没有比这个更板子的了。对于每个石柱拆点成两个,连边限制流量。

 1 #include
 2 using namespace std;
 3 int cnt=2,in[22][22],out[22][22],n,m,d,tms[22][22],ecnt=1,dep[1005];
 4 int fir[1005],l[50005],to[50005],v[50005],x,maxflow,q[1005],t,cntt;
 5 int fab(int p){return p*p;}
 6 void connect(int a,int b,int vv){
 7     l[++ecnt]=fir[a];fir[a]=ecnt;to[ecnt]=b;v[ecnt]=vv;
 8     l[++ecnt]=fir[b];fir[b]=ecnt;to[ecnt]=a;
 9 }
10 int read1(){
11     register int ch=getchar();
12     while(ch<'0'||ch>'3')ch=getchar();
13     return ch-48;
14 }
15 int read2(){
16     register int ch=getchar();
17     while(ch!='L'&&ch!='.')ch=getchar();
18     return ch=='L';
19 }
20 int bfs(){
21     memset(dep,0,sizeof(dep)); dep[1]=q[1]=1; t=1;
22     for(int h=1;h<=t;++h) for(int i=fir[q[h]];i;i=l[i])
23         if(!dep[to[i]]&&v[i]){
24             dep[to[i]]=dep[q[h]]+1;
25             q[++t]=to[i];
26             if(to[i]==2)return 1;
27         }
28     return 0;
29 }
30 int dfs(int p,int flow){
31     if(p==2)return flow;int res=flow;
32     for(int i=fir[p];i;i=l[i]) if(dep[to[i]]==dep[p]+1&&v[i]&&res){
33         int q=dfs(to[i],min(res,v[i]));
34         if(!q)dep[to[i]]=0;
35         res-=q;v[i]-=q;v[i^1]+=q;
36     }
37     return flow-res;
38 }
39 int main(){
40     scanf("%d%d%d",&n,&m,&d);
41     for(int i=1;i<=n;++i) for(int j=1;j<=m;++j){
42             tms[i][j]=read1();
43             if(tms[i][j]) connect(cnt+1,cnt+2,tms[i][j]),
44                 in[i][j]=++cnt,out[i][j]=++cnt;
45         }
46     for(int i=1;i<=n;++i) for(int j=1;j<=m;++j)
47         if(read2()) connect(1,in[i][j],1),cntt++;
48     for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) if(tms[i][j])
49         for(int k=1;k<=n;++k) for(int l=1;l<=m;++l) if(tms[k][l])
50             if(i!=k||j!=l) if(fab(i-k)+fab(j-l)<=fab(d))
51                 connect(out[i][j],in[k][l],1234567);
52     for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) if(tms[i][j])
53         if(i<=d||n-iout[i][j],2,7654321);
54     while(bfs())maxflow+=dfs(1,1234567);
55     printf("%d",cntt-maxflow);
56 }
View Code

 

 

星际战争

二分答案+最大流。检查是否满流即可。

 1 #include
 2 using namespace std;
 3 int ecnt,cnt,atk[55],def[55],can[55][55],fir[123],l[12345],to[12345];
 4 int totd,dep[123],q[123],t,n,m;
 5 double max_flow,v[12345];
 6 #define eps 1e-6
 7 void connect(int a,int b,double vv){
 8     l[++ecnt]=fir[a];fir[a]=ecnt;to[ecnt]=b;v[ecnt]=vv;
 9     l[++ecnt]=fir[b];fir[b]=ecnt;to[ecnt]=a;v[ecnt]=0;
10 }
11 int bfs(){
12     memset(dep,0,sizeof(dep));dep[1]=q[1]=t=1;
13     for(int h=1;h<=t;++h) for(int i=fir[q[h]];i;i=l[i])
14         if(!dep[to[i]]&&v[i]>eps){
15             q[++t]=to[i]; dep[to[i]]=dep[q[h]]+1;
16             if(to[i]==2)return 1;
17         }
18     return 0;
19 }
20 double dfs(int p,double flow){
21     if(p==2)return flow;double res=flow;
22     for(int i=fir[p];i;i=l[i]) if(dep[to[i]]==dep[p]+1&&v[i]>eps&&res>eps){
23         double succ=dfs(to[i],min(res,v[i]));
24         if(succ0;
25         res-=succ;v[i]-=succ;v[i^1]+=succ;
26     }
27     return flow-res;
28 }
29 int main(){
30     scanf("%d%d",&n,&m);
31     for(int i=1;i<=n;++i) scanf("%d",&def[i]),totd+=def[i];
32     for(int i=1;i<=m;++i) scanf("%d",&atk[i]);
33     for(int i=1;i<=m;++i) for(int j=1;j<=n;++j) scanf("%d",&can[i][j]);
34     double l=0,r=100000,mid;
35     while(r-l>eps){
36         mid=(l+r)/2;ecnt=1;max_flow=0;
37         memset(fir,0,sizeof(fir));
38         for(int i=1;i<=m;++i) connect(1,2+i,mid*atk[i]);
39         for(int i=1;i<=n;++i) connect(2+n+i,2,def[i]);
40         for(int i=1;i<=m;++i) for(int j=1;j<=n;++j) if(can[i][j]) connect(2+i,2+n+j,1e9);
41         while(bfs())max_flow+=dfs(1,1e9);
42         if(totd-max_flowelse l=mid;
43     }
44     printf("%.5lf\n",l);
45 }
View Code

 

 

网络吞吐量

最短路。找出所有最短路上的边,然后还是拆点限制流量。

 1 #include
 2 using namespace std;
 3 #define inf 0x3f3f3f3f3f3f3f3f
 4 #define int long long
 5 priority_queueint,int>,vectorint,int> >,greaterint,int> > >que;
 6 int x[505][505],n,m,fir[1234],l[555555],to[555555],v[555555],ecnt=1;
 7 int q[1234],t,dt[555],xx,max_flow,dep[1234];
 8 int _fir[1234],_l[555555],_to[555555],_v[555555],_ecnt=1;
 9 void con(int a,int b,int vv){
10     l[++ecnt]=fir[a];fir[a]=ecnt;to[ecnt]=b;v[ecnt]=vv;
11     l[++ecnt]=fir[b];fir[b]=ecnt;to[ecnt]=a;v[ecnt]=vv;
12 }
13 void _con(int a,int b,int vv){
14     _l[++_ecnt]=_fir[a];_fir[a]=_ecnt;_to[_ecnt]=b;_v[_ecnt]=vv;
15     _l[++_ecnt]=_fir[b];_fir[b]=_ecnt;_to[_ecnt]=a;
16 }
17 int bfs(){
18     memset(dep,0,sizeof dep);t=dep[q[1]=3]=1;
19     for(int h=1;h<=t;++h) for(int i=_fir[q[h]];i;i=_l[i]) if(_v[i]&&!dep[_to[i]]){
20         dep[_to[i]]=dep[q[h]]+1;q[++t]=_to[i];
21         if(_to[i]==n<<1)return 1;
22     }    
23     return 0;
24 }
25 int dfs(int p,int flow){
26     if(p==n<<1)return flow;int res=flow;
27     for(int i=_fir[p];i;i=_l[i])if(res&&_v[i]&&dep[_to[i]]==dep[p]+1){
28         int q=dfs(_to[i],min(res,_v[i]));
29         if(!q)dep[_to[i]]=0;
30         res-=q;_v[i]-=q;_v[i^1]+=q;
31     }
32     return flow-res;
33 }
34 signed main(){
35     scanf("%lld%lld",&n,&m);memset(x,0x3f,sizeof x);memset(dt,0x3f,sizeof dt);
36     for(int i=1,a,b,vx;i<=m;++i)scanf("%lld%lld%lld",&a,&b,&vx),x[a][b]=x[b][a]=min(x[a][b],vx);
37     for(int i=1;i<=n;++i) for(int j=i+1;j<=n;++j) if(x[i][j]!=inf) con(i,j,x[i][j]);
38     que.push(make_pair(0,1));dt[1]=0;
39     while(!que.empty()){
40         int d=que.top().first,p=que.top().second;que.pop();
41         if(p==n)break;if(d!=dt[p])continue;
42         for(int i=fir[p];i;i=l[i])
43             if(dt[to[i]]>d+v[i])dt[to[i]]=d+v[i],que.push(make_pair(dt[to[i]],to[i]));
44     }
45     for(int i=1;i"%lld",&xx),_con(i<<1,i<<1|1,xx);
46     for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) if(dt[i]==dt[j]+x[i][j]) _con(j<<1|1,i<<1,inf);
47     while(bfs())max_flow+=dfs(3,inf);
48     printf("%lld\n",max_flow);
49 }
View Code

 

 

奇怪的游戏

大型分类讨论。棋盘黑白染色。

如果总位置数是偶数,而黑白格子之和不同,无解。

如果黑白和一样,那么二分答案+网络流check。二分的是最后棋盘上都相同的数字是几。答案满足单调性。

如果你能制造出一种局面使棋盘上都是x,那么因为一共偶数个格子,两两配对+1就可以让他们全是x+1。

如果总位置数是奇数,那么如果有解,最终的值也是确定的,解方程。如一共有x个黑格子和x+1个白格子,每次操作黑白都+1。

那么就是$sum_{black}+x==sum_{white}+x+1$。用上面二分答案里的check去看一下x这个值是否合法就行了。

打着挺麻烦的。网络流要根据格子的黑白来分成二分图再跑。

 1 #include
 2 using namespace std;
 3 #define int long long
 4 #define inf 1234567890123450ll
 5 int n,m,T,x[42][42],tot[2],r[42][42],cnt,mx;
 6 int ecnt,fir[2222],l[12345],to[12345],v[12345],q[2222],t,dep[2222];
 7 void connect(int a,int b,int vv){//printf("%lld %lld %lld\n",a,b,vv);
 8     l[++ecnt]=fir[a];fir[a]=ecnt;to[ecnt]=b;v[ecnt]=vv;
 9     l[++ecnt]=fir[b];fir[b]=ecnt;to[ecnt]=a;v[ecnt]=0;
10 }
11 int bfs(){
12     memset(dep,0,sizeof dep);
13     q[1]=t=dep[1]=1;
14     for(int h=1;h<=t;++h)
15         for(int i=fir[q[h]];i;i=l[i])
16             if(!dep[to[i]]&&v[i]) 
17                 dep[to[i]]=dep[q[h]]+1,q[++t]=to[i];
18     return dep[2];
19 }
20 int dfs(int p,int flow){
21     if(p==2)return flow;int res=flow;
22     for(int i=fir[p];i;i=l[i]) 
23         if(dep[to[i]]==dep[p]+1&&v[i]&&res){
24             int q=dfs(to[i],min(res,v[i]));
25             if(!q)dep[to[i]]=0;
26             res-=q;v[i]-=q;v[i^1]+=q;
27         }
28     return flow-res;
29 }
30 bool check(int xx){
31     memset(fir,0,sizeof fir);
32     ecnt=1;int max_flow=0,in=0;
33     for(int i=1;i<=n;++i) for(int j=1;j<=m;++j)
34         if(i+j&1) connect(1,r[i][j],xx-x[i][j]),in+=xx-x[i][j];
35         else connect(r[i][j],2,xx-x[i][j]);
36     for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)if(i+j&1){
37         if(i!=1)connect(r[i][j],r[i-1][j],inf);
38         if(i!=n)connect(r[i][j],r[i+1][j],inf);
39         if(j!=1)connect(r[i][j],r[i][j-1],inf);
40         if(j!=m)connect(r[i][j],r[i][j+1],inf);
41     }
42     while(bfs()) max_flow+=dfs(1,inf);
43     return in==max_flow;
44 }
45 signed main(){
46 //    freopen("game1.in","r",stdin);
47     scanf("%lld",&T);
48     while(T--){
49         scanf("%lld%lld",&n,&m); tot[0]=tot[1]=mx=0; cnt=2;
50         for(int i=1;i<=n;++i) for(int j=1;j<=m;++j)
51             scanf("%lld",&x[i][j]),tot[i+j&1]+=x[i][j],mx=max(mx,x[i][j]),r[i][j]=++cnt;
52         if(n*m&1){
53             if(mx>tot[0]-tot[1])puts("-1");
54             else if(check(tot[0]-tot[1])) printf("%lld\n",(tot[0]-tot[1])*m*n-tot[0]-tot[1]>>1);
55             else puts("-1");
56         }else if(tot[0]!=tot[1])puts("-1");
57         else{
58             int l=mx,r=inf;
59             while(l1)
60                 if(check(l+r>>1))r=l+r>>1;
61                 else l=(l+r>>1)+1;
62             if(check(l))printf("%lld\n",l*n*m-tot[0]-tot[1]>>1);
63             else printf("%lld\n",r*n*m-tot[0]-tot[1]>>1);
64         }
65     }
66 }
View Code

 

 

土兵占领

我写过题解。。。

 

 

紧急疏散evacuate

容易想到二分时间,每一个门都限制mid的流量,加一个最短路,距离小于时间的就连边,看是否能流满。

但是这是不对的。如果有两个人距离门都是2,而二分的时间也是2,判定为有解实际为无解。

具体数据我忘了,大概是这个意思。WA92。

另一个常用思想,还是拆点,不过是根据含义拆点。

把每一个们拆成mid个。表示这扇门在时间t时逃出的机会。那么流向汇点的流量就是1。

然后在每扇门的t到t+1连边,表示现在有人出去了的话就再等1单位时间。

 1 #include
 2 using namespace std;
 3 #define S 16666666
 4 const int dx[]={1,-1,0,0},dy[]={0,0,1,-1};
 5 #define tx x+dx[i]
 6 #define ty y+dy[i]
 7 int fir[S],l[S],to[S],v[S],q[S],dep[S],n,m,E,cntp,cntd,dt[444][22][22];
 8 char s[22][22];int pc,ec,nord[444][444];
 9 void DFS(int fd,int x,int y,int stp){
10     dt[fd][x][y]=stp;
11     for(int i=0;i<4;++i)if(dt[fd][tx][ty]>stp+1&&s[tx][ty]=='.')DFS(fd,tx,ty,stp+1);
12 }
13 void link(int a,int b,int V){l[++ec]=fir[a];fir[a]=ec;to[ec]=b;v[ec]=V;}
14 void con(int a,int b,int V){link(a,b,V);link(b,a,0);}
15 bool bfs(){
16     for(int i=1;i<=E;++i)dep[i]=0;
17     for(int h=1,t=1;h<=t;++h)for(int i=fir[q[h]];i;i=l[i])if(!dep[to[i]]&&v[i])
18         dep[q[++t]=to[i]]=dep[q[h]]+1;
19     return dep[E];
20 }
21 int dfs(int p,int flow){int r=flow;
22     if(p==E)return flow;
23     for(int i=fir[p];i;i=l[i])if(dep[to[i]]==dep[p]+1&&v[i]&&r){
24         int x=dfs(to[i],min(r,v[i]));
25         if(!x)dep[to[i]]=0;
26         v[i]-=x;v[i^1]+=x;r-=x;
27     }return flow-r;
28 }
29 bool chk(int T){
30     pc=0;ec=1;E=cntd*T+cntp+1;
31     for(int i=1;i<=cntd;++i)for(int j=1;j<=T;++j)nord[i][j]=++pc,con(pc,E,1);
32     for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)if(s[i][j]=='.'){
33         con(0,++pc,1);
34         for(int k=1;k<=cntd;++k)if(dt[k][i][j]<=T)con(pc,nord[k][dt[k][i][j]],1);
35     }
36     for(int i=1;i<=cntd;++i)for(int j=1;j1],444);
37     int maxflow=0;while(bfs())maxflow+=dfs(0,444);
38     for(int i=0;i<=E;++i)fir[i]=0;
39     return maxflow==cntp;
40 }
41 main(){dep[0]=1;
42     scanf("%d%d",&n,&m);for(int i=1;i<=n;++i)scanf("%s",s[i]+1);
43     memset(dt,0x3f,sizeof dt);
44     for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)
45         if(s[i][j]=='.')cntp++;else if(s[i][j]=='D')DFS(++cntd,i,j,0);
46     int l=0,r=n*m+1,ans=n*m+1;
47     while(l<=r)if(chk(l+r>>1))ans=r=l+r>>1,r--;else l=(l+r>>1)+1;
48     if(ans==n*m+1)return puts("impossible"),0;printf("%d\n",ans);
49 }
View Code

 

 

狼抓兔子

注意是双向边。

暴力建图跑最小割没什么问题。但是其实复杂度是不对的。

对付这一类问题有一个特殊方法:

适用条件:图可以花在二维平面(纸)上,而且所有边之间的交点都是原图中的结点。这样的话这种图就可以转化为对偶图。

在满足上述条件的图里,二维平面被边分割成了若干多边形。

假设源点S在图的左上角,汇点T在右下角。再连一条inf边从S指向T(从整个图的外围画一个大半圆),会把这个图的外部也分成两部分。简称内部和外部。

把平面上的多边形看作新图的节点(包括被inf边隔开的“内部”与“外部”),建边,边权就是两个多边形之间的公共边的边权。

现在我们求出一条从内部到外部的最短路,它的长度就是最小割。

从含义上理解,你所经过的路径上的边,就是原图中要被割掉的边。要最小,那么就是最短路。

 1 #include
 2 #include
 3 using namespace std;
 4 #define S 1000005
 5 int fir[S],l[S*6],to[S*6],v[S*6],q[S],n,m,o[1005][1005],ec=1,pc,dep[S],ans;
 6 void link(int a,int b,int V){l[++ec]=fir[a];fir[a]=ec;to[ec]=b;v[ec]=V;}
 7 bool bfs(){
 8     for(int i=1;i<=pc;++i)dep[i]=0;dep[1]=q[1]=1;
 9     for(int h=1,t=1;h<=t;++h)for(int i=fir[q[h]];i;i=l[i])if(!dep[to[i]]&&v[i])
10         dep[q[++t]=to[i]]=dep[q[h]]+1;
11     return dep[pc];
12 }
13 int dfs(int p,int flow){
14     if(p==pc)return flow;int r=flow;
15     for(int i=fir[p];i;i=l[i])if(dep[to[i]]==dep[p]+1&&v[i]&&r){
16         int M=dfs(to[i],min(v[i],r));
17         if(!M)dep[to[i]]=0;
18         r-=M;v[i]-=M;v[i^1]+=M;
19     }return flow-r;
20 }
21 main(){
22     scanf("%d%d",&n,&m); for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)o[i][j]=++pc;
23     for(int i=1;i<=n;++i)for(int j=1,x;j"%d",&x),link(o[i][j],o[i][j+1],x),link(o[i][j+1],o[i][j],x);
24     for(int i=1;ifor(int j=1,x;j<=m;++j)scanf("%d",&x),link(o[i][j],o[i+1][j],x),link(o[i+1][j],o[i][j],x);
25     for(int i=1;ifor(int j=1,x;j"%d",&x),link(o[i][j],o[i+1][j+1],x),link(o[i+1][j+1],o[i][j],x);
26     while(bfs())ans+=dfs(1,0x3fffffff);printf("%d\n",ans);
27 }
然而我没有这么写。。。在另一道题会用上的

 

 

切糕

自己想了八百年系列。挺难的。险些颓题解。

最小代价,那么看着像是一个最小割了。

对于每一个纵轴,在上面你会且仅会取一个点,为了表示这种“或”的关系。我们把所有在同一条纵轴上的点“串联”起来。

[专题总结]网络流初步(1)_第1张图片

而相邻格子是有限制的,距离不超过D。即选了(i,j,f(i,j))就必须选(i,j+1,[f(i,j)-D,f(i,j)+D])。为了表示这种“且“的关系,将其并联。

注意到这里限制的区间是连续的一段,所以就像物理的什么导线上的试触法一样,把限制的那一段”接入电路”

[专题总结]网络流初步(1)_第2张图片

图中以“选了(1,1,3)就必须选(1,2,2~3)”为例。如果你割了v(1,1,3)这条边而不割断v(1,2,2),v(1,2,3)之一的话,那么“电流”就会绕着这条路走向终点,不满足最小割。

所以这样就限制住了“割掉这个边,那么就必须割掉另一条纵轴上连续的区间之一”这种条件。

 1 #include
 2 #include
 3 using namespace std;
 4 #define rep() for(int i=1;i<=x;++i)for(int j=1;j<=y;++j)
 5 #define I 0x3fffffff
 6 int w[44][44][44],o[44][44][44],ec=1,pc,fir[66666],l[333333],to[333333],v[333333];
 7 int x,y,z,D,T,ans,q[66666],d[66666];
 8 void link(int a,int b,int V){l[++ec]=fir[a];fir[a]=ec;to[ec]=b;v[ec]=V;}
 9 void con(int a,int b,int V){link(a,b,V);link(b,a,0);}
10 int up(int x){return max(x,0);}
11 int down(int x){return min(x,z);}
12 bool bfs(){
13     for(int i=1;i<=T;++i)d[i]=0;
14     for(int h=1,t=1;h<=t;++h)for(int i=fir[q[h]];i;i=l[i])if(v[i]&&!d[to[i]])
15         d[q[++t]=to[i]]=d[q[h]]+1;
16     return d[T];
17 }
18 int dfs(int p,int flow){
19     if(p==T)return flow;int r=flow;
20     for(int i=fir[p];i;i=l[i])if(v[i]&&r&&d[to[i]]==d[p]+1){
21         int x=dfs(to[i],min(v[i],r));
22         if(!x)d[to[i]]=0;
23         v[i]-=x;v[i^1]+=x;r-=x;
24     }return flow-r;
25 }
26 int main(){
27     scanf("%d%d%d%d",&x,&y,&z,&D);d[0]=1;
28     for(int k=1;k<=z;++k)rep()scanf("%d",&w[i][j][k]);
29     rep()for(int k=0;k<=z;++k)o[i][j][k]=++pc;T=++pc;
30     rep()con(0,o[i][j][0],I),con(o[i][j][z],T,I);
31     rep()for(int k=1;k<=z;++k)con(o[i][j][k-1],o[i][j][k],w[i][j][k]);
32     for(int i=1;ifor(int j=1;j<=y;++j)for(int k=1;k<=z;++k)
33         con(o[i][j][k-1],o[i+1][j][up(k-D-1)],I),con(o[i+1][j][down(k+D)],o[i][j][k],I);
34     for(int i=1;i<=x;++i)for(int j=1;jfor(int k=1;k<=z;++k)
35         con(o[i][j][k-1],o[i][j+1][up(k-D-1)],I),con(o[i][j+1][down(k+D)],o[i][j][k],I);
36     while(bfs())ans+=dfs(0,I);printf("%d\n",ans);
37 }
View Code

 

 

Figure Eight

反正不是网络流。。。瞎写。。。

 1 #include
 2 #include
 3 using namespace std;
 4 #define e 333
 5 char c[e][e];int s[e][e],n,mxsz[e][e][e];long long ans;short u[e][e][e],d[e][e][e];
 6 bool ask(int x,int l,int r){return s[x][r]-s[x-1][r]-s[x][l-1]+s[x-1][l-1]==r-l+1;}
 7 int main(){
 8     scanf("%d",&n);
 9     for(int i=1;i<=n;++i)scanf("%s",c[i]+1);
10     for(int i=1;i<=n;++i)for(int j=1;j<=n;++j)s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+(c[i][j]=='.');
11     for(int l=1;l<=n;++l)for(int r=l+2;r<=n;++r){
12         int lst=0;
13         for(int i=1;i<=n;++i)
14             if(ask(i,l,r)){if(lst)u[i][l][r]=i-lst-1;else lst=i;}
15             else if(c[i][l]=='*'||c[i][r]=='*')lst=0;
16     }
17     for(int len=2;len<=n;++len)for(int l=1,r=l+len;r<=n;++l,++r)for(int i=1;i<=n;++i)
18         mxsz[i][l][r]=max(max(mxsz[i][l+1][r],mxsz[i][l][r-1]),u[i][l][r]?(u[i][l][r]*(r-l-1)):0);
19     for(int l=1;l<=n;++l)for(int r=l+2;r<=n;++r){
20         int lst=0;
21         for(int i=n;i;--i)
22             if(ask(i,l,r)){if(lst)ans=max(ans,(lst-i-1)*(r-l-1ll)*mxsz[i][l][r]);else lst=i;}
23             else if(c[i][l]=='*'||c[i][r]=='*')lst=0;
24     }
25     printf("%lld\n",ans?ans:-1);
26 }
View Code

 

 

最大获利

最常用的最小割模型之一:选点付出一定代价,两个点同时选获得一定收益。

[专题总结]网络流初步(1)_第3张图片

网上有人管这个叫做三叉戟模型。我觉得不错。

上来把所有可能的收益都累加答案。然后求最小割的含义就是:

要么你同时付出A1,A2的代价(都买下来),否则你就会割舍W(1,2)的收益(没都买下来的话就不会获得收益)

 1 #include
 2 #include
 3 using namespace std;
 4 #define S 3000005 
 5 #define M 100000000
 6 int fir[S],l[S],to[S],ec=1,v[S],n,m,ans,dep[S],q[S];
 7 void link(int a,int b,int V){l[++ec]=fir[a];fir[a]=ec;to[ec]=b;v[ec]=V;}
 8 void con(int a,int b,int V){link(a,b,V);link(b,a,0);}
 9 bool bfs(){
10     for(int i=1;i<=n+m+1;++i)dep[i]=0;
11     for(int h=1,t=1;h<=t;++h)for(int i=fir[q[h]];i;i=l[i])if(v[i]&&!dep[to[i]])
12         dep[q[++t]=to[i]]=dep[q[h]]+1;
13     return dep[n+m+1];
14 }
15 int dfs(int p,int flow){
16     if(p==n+m+1)return flow;int r=flow;
17     for(int i=fir[p];i;i=l[i])if(v[i]&&dep[to[i]]==dep[p]+1&&r){
18         int x=dfs(to[i],min(r,v[i]));
19         if(!x)dep[to[i]]=0;
20         v[i]-=x;v[i^1]+=x;r-=x;
21     }return flow-r;
22 }
23 int main(){
24     scanf("%d%d",&n,&m);dep[0]=1;
25     for(int i=1,x;i<=n;++i)scanf("%d",&x),con(0,i,x);
26     for(int i=1,a,b,c;i<=m;++i)scanf("%d%d%d",&a,&b,&c),ans+=c,con(a,i+n,M),con(b,i+n,M),con(i+n,m+n+1,c);
27     while(bfs())ans-=dfs(0,M);printf("%d\n",ans);
28 }
View Code

 

 

happiness

两个三叉戟完事。

[专题总结]网络流初步(1)_第4张图片

“文-文-X-共理”是一个三叉戟。“理-理-Y-同文”是另一个。含义不难理解。

 1 #include
 2 #define S 3000005
 3 #define M 100000000
 4 int n,m,fir[S],l[S],to[S],v[S],o[101][101],pc,ec=1,ans,E,q[S],dep[S];
 5 int min(int a,int b){return aa:b;}
 6 void link(int a,int b,int V){l[++ec]=fir[a];fir[a]=ec;to[ec]=b;v[ec]=V;}
 7 void con(int a,int b,int V){link(a,b,V);link(b,a,0);}
 8 bool bfs(){
 9     for(int i=1;i<=pc;++i)dep[i]=0;
10     for(int h=1,t=1;h<=t;++h)for(int i=fir[q[h]];i;i=l[i])if(!dep[to[i]]&&v[i])
11         dep[q[++t]=to[i]]=dep[q[h]]+1;
12     return dep[E];
13 }
14 int dfs(int p,int flow){
15     if(p==E)return flow;int r=flow;
16     for(int i=fir[p];i;i=l[i])if(r&&v[i]&&dep[to[i]]==dep[p]+1){
17         int x=dfs(to[i],min(v[i],r));
18         if(!x)dep[to[i]]=0;
19         v[i]-=x;v[i^1]+=x;r-=x;
20     }return flow-r;
21 }
22 main(){dep[0]=1;
23     scanf("%d%d",&n,&m); for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)o[i][j]=++pc;E=++pc;
24     for(int i=1;i<=n;++i)for(int j=1,x;j<=m;++j)scanf("%d",&x),con(o[i][j],E,x),ans+=x;
25     for(int i=1;i<=n;++i)for(int j=1,x;j<=m;++j)scanf("%d",&x),con(0,o[i][j],x),ans+=x;
26     for(int i=1;ifor(int j=1,x;j<=m;++j)scanf("%d",&x),con(o[i][j],++pc,M),con(o[i+1][j],pc,M),con(pc,E,x),ans+=x;
27     for(int i=1;ifor(int j=1,x;j<=m;++j)scanf("%d",&x),con(++pc,o[i][j],M),con(pc,o[i+1][j],M),con(0,pc,x),ans+=x;
28     for(int i=1;i<=n;++i)for(int j=1,x;j"%d",&x),con(o[i][j],++pc,M),con(o[i][j+1],pc,M),con(pc,E,x),ans+=x;
29     for(int i=1;i<=n;++i)for(int j=1,x;j"%d",&x),con(++pc,o[i][j],M),con(pc,o[i][j+1],M),con(0,pc,x),ans+=x;
30     while(bfs())ans-=dfs(0,M);printf("%d\n",ans);
31 }
View Code

 

 

employ人员雇佣

两人都雇+2w(i,j),雇一个-w(i,j),一个都不雇+0。建三叉戟。

可以认为:雇一个人需要额外付出w(i,j),而两个人同时雇会获得4w(i,j)。

怎么想到?解方程啊!

 1 #include
 2 using namespace std;
 3 #define int long long
 4 #define inf 12345678901234567ll
 5 int fir[2000005],l[12000005],to[12000005],v[12000005],ecnt=1,cnt,ans;
 6 int q[2000005],dep[2000005],n,t,cost[1005];
 7 void link(int a,int b,int vv){l[++ecnt]=fir[a];to[ecnt]=b;fir[a]=ecnt;v[ecnt]=vv;}
 8 int bfs(){
 9     memset(dep,0,sizeof dep);dep[q[1]=t=1]=1;
10     for(int h=1;h<=t;++h)for(int i=fir[q[h]];i;i=l[i])if(v[i]&&!dep[to[i]])
11         dep[q[++t]=to[i]]=dep[q[h]]+1;
12     return dep[2];
13 }
14 int dfs(int p,int flow){
15     int res=flow;if(p==2)return flow;
16     for(int i=fir[p];i;i=l[i])if(dep[to[i]]==dep[p]+1&&v[i]&&res){
17         int q=dfs(to[i],min(v[i],res));
18         if(!q)dep[to[i]]=0;
19         v[i]-=q;res-=q;v[i^1]+=q;
20     }
21     return flow-res;
22 }
23 signed main(){
24     scanf("%lld",&n);cnt=2+n;
25     for(int i=1;i<=n;++i)scanf("%lld",&cost[i]);
26     for(int i=1,w;i<=n;++i)for(int j=1;j<=n;++j){
27         scanf("%lld",&w);cost[i]+=w;
28         if(i2,
29         link(2+i,++cnt,inf),link(cnt,2+i,0),
30         link(2+j,cnt,inf),link(cnt,2+j,0),
31         link(cnt,2,w<<2),link(2,cnt,0);
32     }
33     for(int i=1;i<=n;++i)link(1,2+i,cost[i]),link(2+i,1,0);//printf("%lld\n",ans);
34     while(bfs())ans-=dfs(1,inf);
35     printf("%lld\n",ans);
36 }
它T掉了

关键在于对于每一对关系都新建了一个点,这样点的数量是$O(n^2)$的。

如果通过直接在两个点之间连边而不是三叉戟的话,就可以降为$O(n)$

解决方法是,雇佣每个人的代价还是$c_i+\sum\limits_{i=1}^{n} w(i,j)$

然后在两个人之间建边,即i向j连$2w(i,j)$。

当然还可以通过QJ数据A掉这道题,只要你不把w为0的边建出来就能没脸AC了。

 1 #include
 2 #define S 5000005
 3 #define M 0x3ffffffffffff
 4 #define int long long
 5 int min(int a,int b){return aa:b;}
 6 int n,c[1001],w[1001][1001],fir[S],l[S],to[S],v[S],ec=1,pc,q[S],dep[S],ans,E;
 7 void link(int a,int b,int V){l[++ec]=fir[a];fir[a]=ec;to[ec]=b;v[ec]=V;}
 8 void con(int a,int b,int V){link(a,b,V);link(b,a,0);}
 9 bool bfs(){
10     for(int i=1;i<=pc;++i)dep[i]=0;
11     for(int h=1,t=1;h<=t;++h)for(int i=fir[q[h]];i;i=l[i])if(!dep[to[i]]&&v[i])
12         dep[q[++t]=to[i]]=dep[q[h]]+1;
13     return dep[E];
14 }
15 int dfs(int p,int flow){
16     if(p==E)return flow;int r=flow;
17     for(int i=fir[p];i;i=l[i])if(dep[to[i]]==dep[p]+1&&v[i]&&r){
18         int x=dfs(to[i],min(r,v[i]));
19         if(!x)dep[to[i]]=0;
20         v[i]-=x;v[i^1]+=x;r-=x;
21     }return flow-r;
22 }
23 main(){
24     scanf("%lld",&n);pc=E=n+1;dep[0]=1;
25     for(int i=1;i<=n;++i)scanf("%lld",&c[i]);
26     for(int i=1;i<=n;++i)for(int j=1;j<=n;++j)scanf("%lld",&w[i][j]);
27     for(int i=1;i<=n;++i)for(int j=1+i;j<=n;++j)if(w[i][j])
28         con(++pc,E,w[i][j]<<2),con(i,pc,M),con(j,pc,M),ans+=w[i][j]<<2,c[i]+=w[i][j],c[j]+=w[i][j];
29     for(int i=1;i<=n;++i)con(0,i,c[i]);
30     while(bfs())ans-=dfs(0,M);printf("%lld\n",ans);
31 }
View Code

 

 

不同的最小割

最小割树的板子。

任选源汇求出最小割x,这个最小割会把集合划分为两个部分S与T。S内的点到T的点的最小割会对x取min。

再对两个集合分治下去,直到集合大小为1。过程中得到的所有最小割就是答案。

这样就只用做$O(n)$次网络流了。

至于怎么求出集合:就是从源点开始dfs,只走没有满流的边(即v还有值的边)所到达的点都属于S,没有dfs到的属于T。

 1 #include
 2 #include
 3 #include<set>
 4 using namespace std;
 5 set<int>ans;
 6 int n,m,fir[888],l[18888],to[18888],v[18888],d[888],q[888],ec=1,S,E,cnt,a[888],al[888],t[888];
 7 void link(int a,int b,int V){l[++ec]=fir[a];fir[a]=ec;to[ec]=b;v[ec]=V;}
 8 bool bfs(){
 9     for(int i=1;i<=n;++i)d[i]=0;q[d[S]=1]=S;
10     for(int h=1,t=1;h<=t;++h)for(int i=fir[q[h]];i;i=l[i])if(v[i]&&!d[to[i]])
11         d[q[++t]=to[i]]=d[q[h]]+1;
12     return d[E];
13 }
14 int dfs(int p,int flow){int r=flow;
15     if(p==E)return flow;
16     for(int i=fir[p];i&&r;i=l[i])if(d[to[i]]==d[p]+1&&v[i]){
17         int x=dfs(to[i],min(v[i],r));
18         if(!x)d[to[i]]=0;
19         v[i]-=x;v[i^1]+=x;r-=x;
20     }return flow-r;
21 }
22 void DFS(int p){
23     al[p]=1;
24     for(int i=fir[p];i;i=l[i])if(v[i]&&!al[to[i]])DFS(to[i]);
25 }
26 int dinic(int x=0){while(bfs())x+=dfs(S,0x3fffffff);return x;}
27 void Divide(int l,int r){
28     if(l>=r)return;
29     for(int i=2;i<=ec;i+=2)v[i]=v[i^1]=v[i]+v[i^1]>>1;
30     S=a[l];E=a[r];ans.insert(dinic());DFS(S);int L=l-1,R=r+1;
31     for(int i=l;i<=r;++i)if(al[a[i]])t[++L]=a[i];else t[--R]=a[i];
32     for(int i=1;i<=n;++i)al[i]=0;
33     for(int i=l;i<=r;++i)a[i]=t[i];
34     Divide(l,L);Divide(R,r);
35 }
36 int main(){
37     scanf("%d%d",&n,&m);
38     for(int i=1,x,y,V;i<=m;++i)scanf("%d%d%d",&x,&y,&V),link(x,y,V),link(y,x,V);
39     for(int i=1;i<=n;++i)a[i]=i;
40     Divide(1,n);printf("%u\n",ans.size());
41 }
View Code

 

 

晨跑

最小费用最大流板子。要注意对于从1到n的直接连边只能走1次。所以边权(流量)不能是inf。

 1 #include
 2 int n,m,fir[444],l[44444],to[44444],pre[444],v[44444],w[44444],q[44444],d[444],iq[444],ec=1;
 3 int day,ans;
 4 void link(int a,int b,int V,int W){l[++ec]=fir[a];fir[a]=ec;to[ec]=b;v[ec]=V;w[ec]=W;}
 5 bool SPFA(){
 6     for(int i=1;i<=(n<<1|1);++i)d[i]=0x3fffffff;d[q[1]=3]=0;
 7     for(int h=1,t=1;h<=t;iq[q[h]]=0,++h)for(int i=fir[q[h]];i;i=l[i])if(d[to[i]]>d[q[h]]+w[i]&&v[i]){
 8         pre[to[i]]=i;d[to[i]]=d[q[h]]+w[i];
 9         if(!iq[to[i]])iq[q[++t]=to[i]]=1;
10     }return d[n<<1]<0x3fffffff;
11 }
12 int main(){
13     scanf("%d%d",&n,&m);
14     for(int i=1,x,y,W;i<=m;++i)scanf("%d%d%d",&x,&y,&W),link(x<<1|1,y<<1,1,W),link(y<<1,x<<1|1,0,-W);
15     for(int i=1;i<=n;++i)link(i<<1,i<<1|1,1,0),link(i<<1|1,i<<1,0,0);
16     while(SPFA()){day++;for(int i=pre[n<<1];i;i=pre[to[i^1]])ans+=w[i],v[i]--,v[i^1]++;}
17     printf("%d %d\n",day,ans);
18 }
View Code

 

 

80人环游世界

上下届无源汇最小费用可行流板子。

啊fixed by LNC:我写的是有源汇的但是我忘记为什么了

 1 #include
 2 int Ss=0,s=1,t=2,St=3,cn=3,n,m,dt[101][101],req[101],P[101][2];
 3 int fir[333],l[666666],to[666666],v[666666],c[666666],cnt=1,ans;
 4 int pre[333],dep[333],q[66666],iq[333];
 5 void link(int a,int b,int w,int C){l[++cnt]=fir[a];fir[a]=cnt;to[cnt]=b;v[cnt]=w;c[cnt]=C;}
 6 bool SPFA(){
 7     for(int i=1;i<=cn;++i)dep[i]=1234567890;
 8     for(int h=1,t=1;h<=t;++h,iq[q[h]]=0)for(int i=fir[q[h]];i;i=l[i])if(v[i]&&dep[to[i]]>dep[q[h]]+c[i]){
 9         dep[to[i]]=dep[q[h]]+c[i];pre[to[i]]=i;
10         if(!iq[to[i]])q[++t]=to[i],iq[to[i]]=1;
11     }
12     return dep[St]!=1234567890;
13 }
14 int main(){
15     scanf("%d%d",&n,&m);
16     for(int i=1;i<=n;++i)scanf("%d",&req[i]);
17     for(int i=1;ifor(int j=i+1;j<=n;++j)scanf("%d",&dt[i][j]);
18     for(int i=1;i<=n;++i)P[i][0]=++cn,P[i][1]=++cn;
19     link(Ss,s,m,0);link(s,Ss,0,0);
20     link(t,St,m,0);link(St,t,0,0);
21     for(int i=1;i<=n;++i)link(s,P[i][0],1234567890,0),link(P[i][0],s,0,0);
22     for(int i=1;i<=n;++i)link(P[i][1],t,1234567890,0),link(t,P[i][1],0,0);
23     for(int i=1;i<=n;++i)link(Ss,P[i][1],req[i],0),link(P[i][1],Ss,0,0);
24     for(int i=1;i<=n;++i)link(P[i][0],St,req[i],0),link(St,P[i][0],0,0);
25     for(int i=1;i<=n;++i)for(int j=i+1;j<=n;++j)if(dt[i][j]!=-1)
26         link(P[i][1],P[j][0],1234567890,dt[i][j]),link(P[j][0],P[i][1],0,-dt[i][j]);
27     while(SPFA())for(int i=pre[St];i;i=pre[to[i^1]])v[i]--,v[i^1]++,ans+=c[i];
28     printf("%d\n",ans);
29 }
View Code

 

 

修车

正难则反。还是拆点。把一个修车师傅拆成n个点,其中点d(i,j)表示第i个修车师傅修的倒数第j辆车。对于一辆时间为t的车,连向这个点的边权为t*j。

因为在它后面的所有车都需要等待t时间。因为它是倒数第j个,所以它后面有(j-1)个,算上他自己有j个,每个人都等t,所以是t×j。

 1 #include
 2 int n,m,t[666][100],num[100][666],cn,fir[6666],l[222222],to[222222],v[222222],c[222222],ans,cnt=1;
 3 int iq[6666],q[222222],dt[6666],pre[6666];
 4 void con(int a,int b,int w,int C){l[++cnt]=fir[a];fir[a]=cnt;to[cnt]=b;v[cnt]=w;c[cnt]=C;}
 5 void link(int a,int b,int C){con(a,b,1,C);con(b,a,0,-C);}
 6 bool SPFA(){
 7     for(int i=1;i<=cn;++i)dt[i]=1234567890;
 8     for(int h=1,t=1;h<=t;++h,iq[q[h]]=0)for(int i=fir[q[h]];i;i=l[i])if(v[i]&&dt[q[h]]+c[i]<dt[to[i]]){
 9         dt[to[i]]=dt[q[h]]+c[i];pre[to[i]]=i;
10         if(!iq[to[i]])iq[to[i]]=1,q[++t]=to[i];
11     }
12     return dt[cn]!=1234567890;
13 }
14 int main(){
15     scanf("%d%d",&m,&n);cn=n;
16     for(int i=1;i<=n;++i)link(0,i,0);
17     for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)num[j][i]=++cn,scanf("%d",&t[i][j]);
18     for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)for(int k=1;k<=n;++k)link(i,num[j][k],t[i][j]*k);
19     cn++;
20     for(int j=1;j<=m;++j)for(int k=1;k<=n;++k)link(num[j][k],cn,0);
21     while(SPFA())for(int i=pre[cn];i;i=pre[to[i^1]])ans+=c[i],v[i]--,v[i^1]++;
22     printf("%.2lf\n",ans*1.0/n);
23 }
View Code

 

 

数字配对

带权匹配问题。。。二分图才可做啊。

可以根据每个数分解质因数后质因子个数是奇偶数来分部。当然同部点不会连边,因为质数个数差为偶数的话就算能整除,商也是至少有2个质因子,是合数不连边。

但是我没有发现,我yy了一种联动边权。网络流在流经边A时也会使B的流量等量减少。

这样的话把每个点放在对偶图两边,一边消耗是另一边也消耗。

在不是二分图时会被hack。

 1 #include
 2 #include
 3 using namespace std;
 4 #define M 10000005
 5 #define E 2*n+1
 6 int a[202],b[202],n,fir[405],l[M],to[M],v[M],ec=3,ans,iq[405],q[M],pre[M];
 7 long long w[M],dt[405],c[202],C;
 8 void link(int a,int b,long long W,int V){l[++ec]=fir[a];fir[a]=ec;to[ec]=b;w[ec]=W;v[ec]=V;}
 9 bool isprime(int x){
10     if(x==1)return 0;
11     for(int i=2;i*i<=x;++i)if(x%i==0)return 0;
12     return 1;
13 }
14 bool SPFA(){
15     for(int i=1;i<=E;++i)dt[i]=0x3ffffffffffff;
16     for(int h=1,t=1;h<=t;iq[q[h]]=0,++h)for(int i=fir[q[h]];i;i=l[i])
17         if(v[i]&&dt[to[i]]>dt[q[h]]+w[i]){
18             dt[to[i]]=dt[q[h]]+w[i],pre[to[i]]=i;
19             if(!iq[to[i]])iq[q[++t]=to[i]]=1;
20         }
21     return dt[E]<0x3ffffffffffff;
22 }
23 main(){scanf("%d",&n);
24     for(int i=1;i<=n;++i)scanf("%d",&a[i]);
25     for(int i=1;i<=n;++i)scanf("%d",&b[i]);
26     for(int i=1;i<=n;++i)scanf("%lld",&c[i]);
27     for(int i=1;i<=n;++i)link(0,i,0,b[i]),link(i,0,0,0),link(i+n,E,0,b[i]),link(E,i+n,0,0);
28     for(int i=1;i<=n;++i)for(int j=1;j<=n;++j)if(a[i]%a[j]==0&&isprime(a[i]/a[j]))
29         link(i,j+n,-c[i]*c[j],M),link(j+n,i,c[i]*c[j],0),link(j,i+n,-c[i]*c[j],M),link(i+n,j,c[i]*c[j],0);
30     while(SPFA()){
31         int x=M;long long c=0;
32         for(int i=pre[E];i;i=pre[to[i^1]])x=min(x,v[i]),c+=w[i];
33         if(C>=x*c){ans+=x,C-=x*c;for(int i=pre[E];i;i=pre[to[i^1]])v[i]-=x,v[i^1]+=x,v[i^2]-=x,v[i^3]+=x;}
34         else{ans+=C/c;break;}
35     }printf("%d\n",ans);
36 }
View Code

 

 

美食节

和《修车》一样。但是会T。

在跑增广路的时候,动态开点。不要都开出来,就可以了。

 1 #include
 2 int n,m,x[450],num[1005][8005],t[450][8005],cn,ans,tot;
 3 int fir[222222],l[20000005],to[20000005],c[20000005],v[20000005],tt[20000005],cnt=1;
 4 int iq[222222],q[222222],dt[222222],pre[222222],al[1005][8005];
 5 void link(int a,int b,int w,int C,int T){l[++cnt]=fir[a];fir[a]=cnt;to[cnt]=b;v[cnt]=w;c[cnt]=C;tt[cnt]=T;}
 6 bool SPFA(){
 7     for(int i=1;i<=cn;++i)dt[i]=1234567890;
 8     for(int h=1,t=1;h<=t;++h,iq[q[h]]=0)for(int i=fir[q[h]];i;i=l[i])if(dt[to[i]]>dt[q[h]]+c[i]&&v[i]){
 9         dt[to[i]]=dt[q[h]]+c[i];pre[to[i]]=i;
10         if(!iq[to[i]])q[++t]=to[i],iq[to[i]]=1;
11     }
12     return dt[cn]!=1234567890;
13 }
14 int main(){
15     scanf("%d%d",&n,&m);cn=n;
16     for(int i=1;i<=n;++i)scanf("%d",&x[i]),link(0,i,x[i],0,0),link(i,0,0,0,0),tot+=x[i];
17     for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)scanf("%d",&t[i][j]);
18     for(int j=1;j<=m;++j)for(int k=1;k<=tot;++k)num[j][k]=++cn;
19     cn++;
20     for(int j=1;j<=m;++j)for(int k=1;k<=tot;++k)link(num[j][k],cn,1,0,0),link(cn,num[j][k],0,0,0);
21     for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)for(int k=1;k<=1;++k)
22         link(i,num[j][k],1,t[i][j]*k,1),link(num[j][k],i,0,-t[i][j]*k,1);
23     while(SPFA()){
24         for(int i=pre[cn];i;i=pre[to[i^1]]){
25             v[i]--,v[i^1]++,ans+=c[i];
26             int x=to[i],y=to[i^1],a;
27             if(!tt[i])continue;
28             if(xcontinue;else x^=y^=x^=y;
29             for(int j=1;j<=m;++j)if(num[j][tt[i]]==y)a=j;
30             if(!al[a][tt[i]+1])for(int j=1;j<=n;++j)link(j,num[a][tt[i]+1],1,t[j][a]*(tt[i]+1),tt[i]+1),link(num[a][tt[i]+1],j,0,-t[j][a]*(tt[i]+1),tt[i]+1);
31             al[a][tt[i]+1]=1;
32         }
33     }
34     printf("%d\n",ans);
35 }
View Code

 

 

 

 

你可能感兴趣的:([专题总结]网络流初步(1))