BZOJ.2668.[CQOI2012]交換棋子(費用流 多路增廣)
阿新 • • 發佈:2018-09-06
lin min 若是 return 模擬 can http 初始 ref 最初白最後黑,那麽會作為終點一次而不作為起點,則連邊\((in\rightarrow x,\frac{lim+1}{2}),(x\rightarrow out,\frac{lim}{2})\);若是黑到白,出會比入多一次;若該點不需要變則流量都是\(\frac{lim}{2}\)。
花費就在\(in\rightarrow x\)與\(x\rightarrow out\)上設\(1\)就行了。 。
(\((u\rightarrow v,w)\)表示\(u\rightarrow v\)的單向邊,容量為\(w\))
題目鏈接
首先黑白棋子的交換等價於黑棋子在白格子圖上移動,都到達指定位置。
在這假設我們知道這題用網絡流做。
那麽黑棋到指定位置就是一條路徑,考慮怎麽用流模擬出這條路徑。
我們發現除了路徑的起點和終點的格子消耗次數為1,路徑上其它點的格子交換次數為\(2\)。
可以想到把每個點拆成\(in\)和\(out\),但這樣無法體現出,作為起點/終點與路徑中其它點的次數消耗差別。
於是拆成三個點,\(in,x,out\),\(x\)代表原點,設點\(x\)流量為\(lim\),\(in,out\)平分流量,邊容量為\(\frac{lim}{2}\)。
直接\(\frac{lim}{2}\)對麽?註意每個點只會作為起點或終點一次。若\(x\)
花費就在\(in\rightarrow x\)與\(x\rightarrow out\)上設\(1\)就行了。
對於起始圖中的黑點\(i\),連邊\((S\rightarrow x(i),1)\);對於最終圖中的黑點\(i\),連邊\((x(i)\rightarrow T,1)\)。
對於相鄰點\(i,j\),連邊\((out(i),in(j),INF)\)
(\((u\rightarrow v,w)\)表示\(u\rightarrow v\)的單向邊,容量為\(w\))
為什麽要拆三個點呢,因為對於可能是起點或終點的點我們無法區分初始流量。
但是如果它是起點或終點,則一定要作為起點或終點走一次,把流量給它就是了;否則是不會有\(1\)的流量的。
如果該點是起點或終點,則\(in\rightarrow out\)流量為\(\frac{lim+1}{2}\),否則流量為\(\frac{lim}{2}\)。這樣就可以只拆兩個點了。
這個相鄰怎麽這麽奇怪??還就給一個水的不行的樣例??
拆成三個點:
#include <queue> #include <cstdio> #include <cctype> #include <cstring> #include <algorithm> #define gc() getchar() const int N=2005,M=N*24,INF=0x3f3f3f3f; int n,m,S,T,id[25][25][3],Enum,H[N],cur[N],nxt[M],to[M],cap[M],cost[M],dis[N],Cost; bool vis[N]; std::queue<int> q; char st[25][25],ed[25][25]; inline int read() { int now=0;register char c=gc(); for(;!isdigit(c);c=gc()); for(;isdigit(c);now=now*10+c-'0',c=gc()); return now; } #define AE(u,v,w,c) to[++Enum]=v,nxt[Enum]=H[u],H[u]=Enum,cap[Enum]=w,cost[Enum]=c,to[++Enum]=u,nxt[Enum]=H[v],H[v]=Enum,cap[Enum]=0,cost[Enum]=-c bool SPFA() { memset(vis,0,sizeof vis); memset(dis,0x3f,sizeof dis); dis[S]=0, q.push(S); while(!q.empty()) { int x=q.front(); q.pop(), vis[x]=0; for(int v,i=H[x]; i; i=nxt[i]) if(cap[i] && dis[v=to[i]]>dis[x]+cost[i]) dis[v]=dis[x]+cost[i], !vis[v]&&(q.push(v),vis[v]=1); } return dis[T]<INF; } int DFS(int x,int f) { if(x==T) return f; vis[x]=1; for(int &i=cur[x],v,tmp; i; i=nxt[i]) if(cap[i] && !vis[v=to[i]] && dis[v]==dis[x]+cost[i]) if(tmp=DFS(v,std::min(cap[i],f))) return cap[i]-=tmp,cap[i^1]+=tmp,Cost+=tmp*cost[i],tmp; return 0; } int MCMF() { int res=0; while(SPFA()) { for(int i=S; i<=T; ++i) cur[i]=H[i]; while(int tmp=DFS(S,INF)) res+=tmp; } return res; } int main() { n=read(),m=read(),Enum=1,S=0,T=n*m*3+1; for(int i=1; i<=n; ++i) scanf("%s",st[i]+1); for(int i=1; i<=n; ++i) scanf("%s",ed[i]+1); int tot=0; for(int i=1; i<=n; ++i) for(int j=1; j<=m; ++j) id[i][j][0]=++tot,id[i][j][1]=++tot,id[i][j][2]=++tot; int tot1=0, tot2=0; for(int i=1; i<=n; ++i)//0:x 1:in 2:out { char c=gc(); for(; !isdigit(c); c=gc()); for(int j=1; j<=m; ++j, c=gc()) { const char s=st[i][j],t=ed[i][j]; const int x=id[i][j][0],in=id[i][j][1],out=id[i][j][2],lim=c-'0'; if(s=='1') AE(S,x,1,0), ++tot1; if(t=='1') AE(x,T,1,0), ++tot2; if(s=='1'&&t=='0') AE(in,x,lim>>1,1), AE(x,out,lim+1>>1,1); else if(s=='0'&&t=='1') AE(in,x,lim+1>>1,1), AE(x,out,lim>>1,1); else AE(in,x,lim>>1,1), AE(x,out,lim>>1,1); if(i<n) AE(out,id[i+1][j][1],INF,0), AE(id[i+1][j][2],in,INF,0);//同色的當然也可以連邊(想啥呢→_→) if(i<n&&j<m) AE(out,id[i+1][j+1][1],INF,0), AE(id[i+1][j+1][2],in,INF,0); if(i<n&&j>1) AE(out,id[i+1][j-1][1],INF,0), AE(id[i+1][j-1][2],in,INF,0); if(j<m) AE(out,id[i][j+1][1],INF,0), AE(id[i][j+1][2],in,INF,0); } } printf("%d\n",(tot1==tot2&&MCMF()==tot1)?Cost>>1:-1); return 0; }
拆成兩個點:
BZOJ.2668.[CQOI2012]交換棋子(費用流 多路增廣)