[专题总结]网络流初步(2)
通过颓了一个题解,终于也搞定了这个专题。
大神都颓题解。
——By skyh
无限之环
颓题解就行了。
建图是真的恶心。不很好想。
还是自己想到了一部分,就是看到题目里说“直线形不能转”就感觉要分类讨论。
无非就那么几类:空格,单个管,L型,直线型,T型,十字形
我一直以为这道题是最小割,然后我就死掉了,思维僵化,实际上这道题是费用流。
惯性拆点,每个点拆成5个,分别是原点以及四个方向。
而且还要染色,就是那种棋盘黑白染色。
根据水管最开始的方向,将原点与剩下的4个点连边。并且根据黑白颜色讲源汇与原点连边。(我是白连源黑连汇)
然后考虑旋转,分类讨论:
单个管:这个好说,讲原有方向分别向其它方向连边,对立方向费用为2其余费用为1。
L型:它有3种旋转,改变一个支管的方向费用为1,全都改变为2,所以关键就在于改变了几个管。
以上右为例,上向下连1费用边,右向左连1费用边,这样就做到了上述要求。(注意不能是上向左,下向右,考虑实际含义)
直线型:不能旋转,不连边
T型:和单个管差不多。
十字型:不用连。
这样的话,黑白染色之后跑流,如果流量==水管接头个数/2,那么就有解,就输出最小费用,否则0。
1 #include<cstdio> 2 #include<iostream> 3 using namespace std; 4 const int xx[]={-1,0,1,0},yy[]={0,1,0,-1}; 5 #define X i+xx[p] 6 #define Y j+yy[p] 7 #define S 1000005 8 #define k knd[i][j] 9 #define O o[i][j] 10 #define Q i+j&1^1 11 int l[S],to[S],v[S],w[S],fir[S],o[2002][2002][5],ec=1,pc,n,m,E,tot,knd[2005][2005],ans; 12 int d[S],pre[S],q[S],iq[S]; 13 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;} 14 void con(int a,int b,int V,int W,int opt=0){if(opt)a^=b^=a^=b;link(a,b,V,W);link(b,a,0,-W);} 15 bool SPFA(){ 16 for(int i=1;i<=E;++i)d[i]=S; 17 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]){ 18 d[to[i]]=d[q[h]]+w[i];pre[to[i]]=i; 19 if(!iq[to[i]])iq[q[++t]=to[i]]=1; 20 }return d[E]<S; 21 } 22 int main(){ 23 scanf("%d%d",&n,&m); 24 for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)for(int p=0;p<=4;++p)o[i][j][p]=++pc; 25 E=++pc; 26 for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)if(i+j&1)con(0,O[0],4,0);else con(O[0],E,4,0); 27 for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)scanf("%d",&k); 28 for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)for(int s=0;s<4;++s)if(k&1<<s)con(O[0],O[s+1],1,0,Q),tot++; 29 for(int i=1;i<=n;++i)for(int j=1;j<=m;++j){ 30 if(k== 1)con(O[1],O[2],1,1,Q),con(O[1],O[3],1,2,Q),con(O[1],O[4],1,1,Q); 31 if(k== 2)con(O[2],O[1],1,1,Q),con(O[2],O[3],1,1,Q),con(O[2],O[4],1,2,Q); 32 if(k== 4)con(O[3],O[2],1,1,Q),con(O[3],O[4],1,1,Q),con(O[3],O[1],1,2,Q); 33 if(k== 8)con(O[4],O[2],1,2,Q),con(O[4],O[3],1,1,Q),con(O[4],O[1],1,1,Q); 34 if(k== 3)con(O[2],O[4],1,1,Q),con(O[1],O[3],1,1,Q); 35 if(k== 6)con(O[3],O[1],1,1,Q),con(O[2],O[4],1,1,Q); 36 if(k==12)con(O[4],O[2],1,1,Q),con(O[3],O[1],1,1,Q); 37 if(k== 9)con(O[1],O[3],1,1,Q),con(O[4],O[2],1,1,Q); 38 if(k==14)con(O[2],O[1],1,1,Q),con(O[4],O[1],1,1,Q),con(O[3],O[1],1,2,Q); 39 if(k==13)con(O[3],O[2],1,1,Q),con(O[1],O[2],1,1,Q),con(O[4],O[2],1,2,Q); 40 if(k==11)con(O[2],O[3],1,1,Q),con(O[4],O[3],1,1,Q),con(O[1],O[3],1,2,Q); 41 if(k== 7)con(O[1],O[4],1,1,Q),con(O[3],O[4],1,1,Q),con(O[2],O[4],1,2,Q); 42 } 43 for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)for(int p=0;p<4;++p)if(1<=X&&X<=n&&1<=Y&&Y<=m) 44 con(O[p+1],o[X][Y][(p^2)+1],1,0); 45 while(SPFA()){tot-=2;for(int i=pre[E];i;i=pre[to[i^1]])ans+=w[i],v[i]--,v[i^1]++;} 46 printf("%d\n",tot?-1:ans); 47 }
星际竞速
上下界无源汇最小费用可行流板子。
1 #include<cstdio> 2 #include<algorithm> 3 using namespace std; 4 #define E 2*n+1 5 #define S 2*n+2 6 #define Q 333333 7 int n,m,fir[Q],l[Q],to[Q],v[Q],w[Q],ec=1,C,q[Q],d[Q],iq[Q],pre[Q]; 8 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;} 9 bool SPFA(){ 10 for(int i=1;i<=S;++i)d[i]=0x3fffffff; 11 for(int h=1,t=1;h<=t;iq[q[h]]=0,++h)for(int i=fir[q[h]];i;i=l[i])if(v[i]&&d[to[i]]>d[q[h]]+w[i]){ 12 d[to[i]]=d[q[h]]+w[i];pre[to[i]]=i; 13 if(!iq[to[i]])iq[q[++t]=to[i]]=1; 14 }return d[E]<0x3fffffff; 15 } 16 int main(){ 17 scanf("%d%d",&n,&m); 18 link(0,S,1,0);link(S,0,0,0); 19 for(int i=1,x;i<=n;++i)scanf("%d",&x), 20 link(S,i,1,x),link(i,S,0,-x),link(i+n,S,1,0),link(S,i+n,0,0), 21 link(i,E,1,0),link(E,i,0,0),link(0,i+n,1,0),link(i+n,0,0,0); 22 for(int i=1,a,b,c;i<=m;++i){ 23 scanf("%d%d%d",&a,&b,&c);if(a>b)a^=b^=a^=b; 24 link(a+n,b,1,c);link(b,a+n,0,-c); 25 } 26 while(SPFA())for(int i=pre[E];i;i=pre[to[i^1]])C+=w[i],v[i]--,v[i^1]++; 27 printf("%d\n",C); 28 }
千钧一发
看起来很像《数字配对》?然后我又用鬼畜的联动边权解决了。
尝试,看那两个条件,想(很长)一段时间,就知道可以按照奇偶性分成二分图。
1 #include<cstdio> 2 #include<cmath> 3 #include<iostream> 4 using namespace std; 5 #define M 1000000000 6 #define E 2*n+1 7 int a[2005],b[2005],n,ec=3,ans,q[2005],d[2005],fir[2005],l[2000005],to[2000005],v[2000005]; 8 void link(int a,int b,int V){l[++ec]=fir[a];fir[a]=ec;to[ec]=b;v[ec]=V;} 9 bool bfs(){ 10 for(int i=1;i<=E;++i)d[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]&&!d[to[i]]) 12 d[q[++t]=to[i]]=d[q[h]]+1; 13 return d[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(v[i]&&d[to[i]]==d[p]+1&&r){ 18 int x=dfs(to[i],min(v[i],r)); 19 if(!x)d[to[i]]=0; 20 v[i]-=x;v[i^1]+=x,v[i^2]-=x;v[i^3]+=x;r-=x; 21 }return flow-r; 22 } 23 bool ju(long long x){int p=sqrt(x);return 1ll*p*p==x;} 24 int gcd(int a,int b){return b?gcd(b,a%b):a;} 25 int main(){ 26 scanf("%d",&n);d[0]=1; 27 for(int i=1;i<=n;++i)scanf("%d",&a[i]); 28 for(int i=1;i<=n;++i)scanf("%d",&b[i]),ans+=b[i]; 29 for(int i=1;i<=n;++i)link(0,i,b[i]),link(i,0,0),link(n+i,E,b[i]),link(E,n+i,0); 30 for(int i=1;i<=n;++i)for(int j=i+1;j<=n;++j)if(ju(1ll*a[i]*a[i]+1ll*a[j]*a[j])&&gcd(a[i],a[j])==1) 31 link(i,j+n,M),link(j+n,i,0),link(j,i+n,M),link(i+n,j,0); 32 while(bfs())ans-=dfs(0,M);printf("%d\n",ans); 33 }
老C的方块
LNC大喊标签被我听到了,然后稍微想一下就会了。
观察,只有竖线没有横线,也许可用?
再发现,他讨厌的图形都被竖线劈成两半,左右都是2个方块。
再发现,其实就是左边距离为2,1,右边距离为1,2的4个点构成一个讨厌的图形。
最小割。四色染色。
这样编号后,所有讨厌的图形都可以表示为ABCD。然后建四分图就好了。
1 #include<cstdio> 2 #include<map> 3 using namespace std; 4 map<pair<int,int>,int>M; 5 const int dx[]={0,0,1,-1},dy[]={1,-1,0,0}; 6 #define tx X[i]+dx[j] 7 #define ty Y[i]+dy[j] 8 #define inf 0x3fffffff 9 #define S 13333333 10 int r,c,n,X[S],Y[S],C[S],ans,fir[S],l[S],to[S],v[S],ec=1,d[S],q[S]; 11 int knd(int x,int y){return x&1?y&3:1000005-y&3;} 12 void link(int a,int b,int V){l[++ec]=fir[a];fir[a]=ec;to[ec]=b;v[ec]=V;} 13 void con(int a,int b,int V){link(a,b,V);link(b,a,0);} 14 bool bfs(){ 15 for(int i=1;i<=(n<<1|1);++i)d[i]=0; 16 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]]) 17 d[q[++t]=to[i]]=d[q[h]]+1; 18 return d[1]; 19 } 20 int dfs(int p,int flow){ 21 int r=flow;if(p==1)return r; 22 for(int i=fir[p];i&&r;i=l[i])if(v[i]&&d[to[i]]==d[p]+1){ 23 int x=dfs(to[i],min(v[i],r)); 24 if(!x)d[to[i]]=0; 25 v[i]-=x;v[i^1]+=x;r-=x; 26 }return flow-r; 27 } 28 int main(){ 29 scanf("%d%d%d",&r,&c,&n);d[0]=1; 30 for(int i=1;i<=n;++i)scanf("%d%d%d",&Y[i],&X[i],&C[i]),M[make_pair(X[i],Y[i])]=i; 31 for(int i=1;i<=n;++i)con(i<<1,i<<1|1,C[i]); 32 for(int i=1;i<=n;++i)if(!knd(X[i],Y[i]))con(0,i<<1,inf); 33 else for(int j=0;j<4;++j)if(M[make_pair(tx,ty)]&&knd(tx,ty)==knd(X[i],Y[i])-1)con(M[make_pair(tx,ty)]<<1|1,i<<1,inf); 34 for(int i=1;i<=n;++i)if(knd(X[i],Y[i])==3)con(i<<1|1,1,inf); 35 while(bfs())ans+=dfs(0,inf);printf("%d\n",ans); 36 }
线性代数
解出来,发现式子的含义就是$\sum\limits A_i \times A_j \times B_{i,j} - \sum\limits C_i \times A_i$
选一个数付出C代价,同时选得到B收益。
暴力。
1 #include<cstdio> 2 #include<iostream> 3 using namespace std; 4 #define M 40000000 5 int n,fir[255555],l[1555555],to[1555555],v[1555555],q[1555555],d[1555555],E,pc,ec=1; 6 int B[555][555],C[555],ans; 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,V);} 9 bool bfs(){ 10 for(int i=1;i<=E;++i)d[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]&&!d[to[i]]) 12 d[q[++t]=to[i]]=d[q[h]]+1; 13 return d[E]; 14 } 15 int dfs(int p,int flow){ 16 int r=flow;if(p==E)return flow; 17 for(int i=fir[p];i&&r;i=l[i])if(v[i]&&d[to[i]]==d[p]+1){ 18 int x=dfs(to[i],min(v[i],r)); 19 if(!x)d[to[i]]=0; 20 v[i]-=x;v[i^1]+=1;r-=x; 21 }return flow-r; 22 } 23 int main(){ 24 scanf("%d",&n);pc=n;E=n*n+n+1; 25 for(int i=1;i<=n;++i)for(int j=1,x;j<=n;++j)scanf("%d",&B[i][j]),ans+=B[i][j]; 26 for(int i=1,x;i<=n;++i)scanf("%d",&x),con(0,i,x); 27 for(int i=1;i<=n;++i)for(int j=1;j<=n;++j)link(i,++pc,M),link(j,pc,M),link(pc,E,B[i][j]); 28 while(bfs())ans-=dfs(0,M);printf("%d\n",ans); 29 }
优化建边。
1 #include<cstdio> 2 #include<iostream> 3 using namespace std; 4 #define M 40000000 5 #define E n+1 6 int n,fir[255555],l[1555555],to[1555555],v[1555555],q[1555555],d[1555555],ec=1; 7 int B[555][555],C[555],ans,b[555]; 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,V);} 10 bool bfs(){ 11 for(int i=1;i<=E;++i)d[i]=0; 12 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]]) 13 d[q[++t]=to[i]]=d[q[h]]+1; 14 return d[E]; 15 } 16 int dfs(int p,int flow){ 17 int r=flow;if(p==E)return flow; 18 for(int i=fir[p];i&&r;i=l[i])if(v[i]&&d[to[i]]==d[p]+1){ 19 int x=dfs(to[i],min(v[i],r)); 20 if(!x)d[to[i]]=0; 21 v[i]-=x;v[i^1]+=1;r-=x; 22 }return flow-r; 23 } 24 int main(){ 25 scanf("%d",&n); 26 for(int i=1;i<=n;++i)for(int j=1,x;j<=n;++j)scanf("%d",&B[i][j]),ans+=B[i][j],b[i]+=B[i][j]; 27 for(int i=1,x;i<=n;++i)scanf("%d",&x),con(0,i,x),con(i,E,b[i]); 28 for(int i=1;i<=n;++i)for(int j=1;j<=n;++j)con(i,j,B[i][j]); 29 while(bfs())ans-=dfs(0,M);printf("%d\n",ans); 30 }
海拔
猜测:你设置的高度只有0和1
口胡证明:对于任意一个格子,它的高度值一定至少与上下左右四个格子之一相同,那样的话答案不会变差
推下去,所以所有格子的高度要么和左上角相同,要么和右下角相同。
现在的问题就是把每个点赋值0或1使答案尽量小。
0和1一定都是一个联通块,不存在孤立的情况。否则把孤立的0或1变成另外一个数答案也不会变差。
所以问题就是:将整个图分割为两个联通块。
最小割。T90。
发现是在二维平面上的,所以采取对偶图优化。
1 #include<cstdio> 2 #include<iostream> 3 #include<queue> 4 using namespace std; 5 priority_queue<pair<int,int> >q; 6 #define M 4000000 7 int n,pc,ec=1,E,fir[M],l[M],to[M],v[M],d[M],o[555][555]; 8 void link(int a,int b,int V){l[++ec]=fir[a];fir[a]=ec;to[ec]=b;v[ec]=V;} 9 int main(){ 10 scanf("%d",&n); 11 for(int i=1;i<=n;++i)for(int j=1;j<=n;++j)o[i][j]=++pc,d[pc]=M<<7;d[E=++pc]=M<<7; 12 for(int i=1;i<=n;++i)o[0][i]=o[i][n+1]=E; 13 for(int i=0;i<=n;++i)for(int j=1,x;j<=n;++j)scanf("%d",&x),link(o[i+1][j],o[i][j],x); 14 for(int i=1;i<=n;++i)for(int j=0,x;j<=n;++j)scanf("%d",&x),link(o[i][j],o[i][j+1],x); 15 for(int i=0;i<=n;++i)for(int j=1,x;j<=n;++j)scanf("%d",&x),link(o[i][j],o[i+1][j],x); 16 for(int i=1;i<=n;++i)for(int j=0,x;j<=n;++j)scanf("%d",&x),link(o[i][j+1],o[i][j],x); 17 q.push(make_pair(0,0)); 18 while(!q.empty()){ 19 int p=q.top().second,dt=-q.top().first;q.pop(); 20 if(dt!=d[p])continue; 21 for(int i=fir[p];i;i=l[i])if(d[to[i]]>dt+v[i])q.push(make_pair(-(d[to[i]]=dt+v[i]),to[i])); 22 }printf("%d\n",d[E]); 23 }