「清华集训 2017」无限之环
无限之WA
https://www.luogu.org/problemnew/show/P4003
本题如果知道是网络流的话,其实建图不算特别神奇,但是比较麻烦。
数据范围过大,插头dp不能处理,而且是一个网格图,考虑网络流。
先看是不是二分图?
每个格子只会和相邻四个格子发生关系
所以,黑白染色正好。
i+j为偶数左部点,i+j为奇数右部点
不漏水是什么?
每个管道的四个口都能和别的接好。
S向左,右向T连口数的容量,费用为0的边
考虑怎么能把左右连在一起。
考虑要上下左右四个方向匹配,那么必然还要拆点(但是这些点之间是并列关系)。
每个本点向四个分点之间考虑连边(在这里考虑旋转以及费用)
考虑通过流量流过来表示选择旋转与否,恰当的边赋恰当的值。
sz=1:
自己方向是(1,0)相邻方向(1,1),对面(1,2)
sz=2:
直线型:自己方向(1,0),另外两个不连(因为不能转),
非直线型:自己两个方向(1,0),每个自己方向+2(-2),连(1,1),(手动模拟一下这样选择的四种流法对应四种旋转的位置)
sz=3:
自己(1,0),剩下一个0位置,距离为1的向它连(1,1),距离为2的向它连(1,2)
sz=4
四个方向(1,0);
(注意,右边的单向边方向和左边的完全相反)
然后左右分点之间相邻的关系上下,左右,下上,右左,左分点连到对应的右分点。
然后跑最小费用最大流。
至于-1
先判断黑格口数是不是等于白格口数
然后如果最大流不是口数(满流)的话,就-1
(其实数据没有-1的点23333~~~)
这样,一条流的意义是什么?左边的某个口和右边的某个口,通过旋转或者不旋转连接在了一起,同时两个管道的需求都少了1
由于所有边的流量都是1(除了和ST连的),所以每个口只会流出1流,减少1的需求,
如果最后满流
那么意味着,所有的口都满足了自己的需求,即每个口都连上了。而且每个流都合法。
出错点:
1.数组开小了。。。。has[4],四位二进制数,少开一位。。。(这个导致开O2之后超级厌氧,全部输出-1WA掉,一定程度上转移了查错重心。。。)
2.提取四位二进制数的时候,习惯性地写成了while(tmp) has[++tot]=tmp%2,tmp>>=1;然鹅,最高位是0的话,没有提取完4位就break了。而且has没有memset,高位就存上了之前可能的1.。。。。导致WA死。。
其实开始找规律一点没错。。。。但是由于while高位0的锅,以为找错了,,,最后还打了暴力判断。。。。
代码:(得开O2)
#pragma GCC optimize(2) #pragma GCC optimize(3) #pragma GCC optimize("Ofast") #include<bits/stdc++.h> #define il inline #define reg register int #define numb (ch^'0') using namespace std; typedef long long ll; il void rd(int &x){ char ch;bool fl=false; while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true); for(x=numb;isdigit(ch=getchar());x=x*10+numb); (fl==true)&&(x=-x); } namespace Miracle{ const int N=2000+2; const int inf=0x3f3f3f3f; int n,m,s,t; struct node{ int nxt,to; int w,v; }e[(5*N+4*N+N*6)*2]; int hd[5*N],cnt=1; void add(int x,int y,int w,int v){ e[++cnt].nxt=hd[x]; e[cnt].to=y; e[cnt].w=w; e[cnt].v=v; hd[x]=cnt; e[++cnt].nxt=hd[y]; e[cnt].to=x; e[cnt].w=0; e[cnt].v=-v; hd[y]=cnt; } int incf[5*N],dis[5*N]; int pre[5*N]; bool vis[5*N]; queue<int>q; bool spfa(){ while(!q.empty()) q.pop(); memset(vis,0,sizeof vis); memset(dis,inf,sizeof dis); dis[s]=0; incf[s]=inf; pre[s]=0; q.push(s); while(!q.empty()){ int x=q.front();q.pop(); vis[x]=0; for(reg i=hd[x];i;i=e[i].nxt){ int y=e[i].to; if(e[i].w&&dis[y]>dis[x]+e[i].v){ dis[y]=dis[x]+e[i].v; pre[y]=i; incf[y]=min(e[i].w,incf[x]); if(!vis[y]){ vis[y]=1; q.push(y); } } } } if(dis[t]==inf) return false; return true; } int ans,maxflow; void upda(){ int x=t; while(pre[x]){ e[pre[x]].w-=incf[t]; e[pre[x]^1].w+=incf[t]; x=e[pre[x]^1].to; } ans+=incf[t]*dis[t]; maxflow+=incf[t]; //cout<<" ans "<<ans<<" "<<maxflow<<endl; } int has[10],tot; int mp[N][N]; int sz[N]; int num(int x,int y,int k){//5 is itself return ((x-1)*m+y-1)*5+k; } int main(){ rd(n);rd(m); s=0,t=n*m*5+1; for(reg i=1;i<=16;++i){ sz[i]=sz[i>>1]+(i&1); } int le=0,ri=0; for(reg i=1;i<=n;++i){ for(reg j=1;j<=m;++j){ rd(mp[i][j]); if((i+j)%2==0) add(s,num(i,j,5),sz[mp[i][j]],0),le+=sz[mp[i][j]]; else add(num(i,j,5),t,sz[mp[i][j]],0),ri+=sz[mp[i][j]]; } } if(le!=ri){ printf("-1");return 0; } for(reg i=1;i<=n;++i){ for(reg j=1;j<=m;++j){ for(reg l=1;l<=4;++l){ has[l]=(mp[i][j]>>(l-1))&1; } int now=num(i,j,5); tot=sz[mp[i][j]]; if((i+j)%2==0){ switch(tot){ case 0:{ break; } case 1:{ int pos=0; //cout<<" find "<<mp[i][j]<<" :: "<<has[1]<<" "<<has[2]<<" "<<has[3]<<" "<<has[4]<<endl; for(reg l=1;l<=4;++l) if(has[l]) { pos=l; } add(now,num(i,j,pos),1,0); add(now,num(i,j,pos%4+1),1,1); add(now,num(i,j,pos==1?4:pos-1),1,1); add(now,num(i,j,(pos+2<=4)?pos+2:pos-2),1,2); break; } case 2:{ if(mp[i][j]==5||mp[i][j]==10){ // cout<<" dkfjdf "<<endl; for(reg l=1;l<=4;++l){ if(has[l]) add(now,num(i,j,l),1,0); } }else{ for(reg l=1;l<=4;++l){ if(has[l]) { add(now,num(i,j,l),1,0); add(num(i,j,l),num(i,j,(l+2<=4)?l+2:l-2),1,1); } } } break; } case 3:{ for(reg l=1;l<=4;++l){ if(has[l]){ add(now,num(i,j,l),1,0); }else{ add(num(i,j,(l+1)<=4?l+1:l-3),num(i,j,l),1,1); add(num(i,j,(l+3)<=4?l+3:l-1),num(i,j,l),1,1); add(num(i,j,(l+2)<=4?l+2:l-2),num(i,j,l),1,2); } } break; } case 4:{ for(reg l=1;l<=4;++l){ add(now,num(i,j,l),1,0); } break; } } }else{ switch(tot){ case 0:{ break; } case 1:{ int pos=0; for(reg l=1;l<=4;++l) if(has[l]){ pos=l; } add(num(i,j,pos),now,1,0); add(num(i,j,pos%4+1),now,1,1); add(num(i,j,pos==1?4:pos-1),now,1,1); add(num(i,j,(pos+2<=4)?pos+2:pos-2),now,1,2); break; } case 2:{ if(mp[i][j]==5||mp[i][j]==10){ for(reg l=1;l<=4;++l){ if(has[l]) add(num(i,j,l),now,1,0); } }else{ for(reg l=1;l<=4;++l){ if(has[l]) { add(num(i,j,l),now,1,0); add(num(i,j,(l+2<=4)?l+2:l-2),num(i,j,l),1,1); } } } break; } case 3:{ for(reg l=1;l<=4;++l){ if(has[l]){ add(num(i,j,l),now,1,0); }else{ add(num(i,j,l),num(i,j,(l+1)<=4?l+1:l-3),1,1); add(num(i,j,l),num(i,j,(l+3)<=4?l+3:l-1),1,1); add(num(i,j,l),num(i,j,(l+2)<=4?l+2:l-2),1,2); } } break; } case 4:{ for(reg l=1;l<=4;++l){ add(num(i,j,l),now,1,0); } break; } } } if((i+j)%2==0){ if(i>1) add(num(i,j,1),num(i-1,j,3),1,0); if(i<n) add(num(i,j,3),num(i+1,j,1),1,0); if(j>1) add(num(i,j,4),num(i,j-1,2),1,0); if(j<m) add(num(i,j,2),num(i,j+1,4),1,0); } } } while(spfa()) upda(); //cout<<" maxflow "<<maxflow<<endl; if(maxflow!=le){ puts("-1");return 0; } printf("%d",ans); return 0; } } signed main(){ Miracle::main(); return 0; } /* Author: *Miracle* Date: 2018/12/14 21:08:15 */
总结:
这个题的突破口的话,
发现插头dp不行,那么网格图,可能就是网络流(数据范围也支持)
黑白染色可以。那么考虑最终合法的结果是什么意义。然后处理好旋转连边。
(发现没有,直线型为什么不能转?因为这样会同时转2个点!网络流没办法处理这种旋转(除非你大力讨论))