「清華集訓 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個點!網路流沒辦法處理這種旋轉(除非你大力討論))