P2774 方格取數問題 題解
阿新 • • 發佈:2022-04-21
這道題目解法很妙,主要用到了最大和 \(=\) 總和 \(-\) 最小捨棄和。
最小捨棄和即最小割(我們構造出一個二分圖滿足割掉 \(=\) 捨去),我們又知最小割 \(=\) 最大流,所以是一道最大流問題。
考慮建圖:
我們發現,只要兩個點相鄰(不相容),那麼這兩個點的橫縱座標和的奇偶性一定不同,即 \((x_i+y_i)\bmod 2 \neq (x_j+y_j)\bmod 2\),所以我們可以把整個圖分成 \((x_i+y_i)\bmod 2=0\) 的點集和 \((x_i+y_i)\bmod 2=1\) 的點集。這樣就構成了二分圖。
考慮構圖:
將這兩個點集分別全部連向 \(S\)
點選檢視程式碼
#include<iostream> #include<cstdio> #include<cstring> #include<queue> #include<vector> using namespace std; const int N=20000+21,INF=0x3f3f3f3f; struct Edge{int u,v,w,nxt;}e[N*100]; struct Node{int u,val;bool operator <(const Node &a)const{return val<a.val;};}; priority_queue<Node>q;//HLPP用優先佇列優化 int dx[4]={-1,1,0,0},dy[4]={0,0,-1,1}; int h[N],gap[N],flow[N],d[N]; bool vis[N]; int n,m,s,t,sum,tot=1; inline int getnum(int x,int y){return (x-1)*m+y;} inline bool check(int x,int y){return x>=1&&x<=n&&y>=1&&y<=m;} inline void add(int u,int v,int w){//別忘了反向邊 e[++tot]=(Edge){u,v,w,h[u]};h[u]=tot; e[++tot]=(Edge){v,u,0,h[v]};h[v]=tot; } bool bfs(){ memset(d,-1,sizeof(d)); queue<int>Q; Q.push(t);//HLPP反向建邊 d[t]=0; while(!Q.empty()){ int u=Q.front(); Q.pop(); for(int i=h[u];i;i=e[i].nxt){ int v=e[i].v,w=e[i^1].w;//注意反向邊 if(d[v]==-1&&w){ d[v]=d[u]+1; Q.push(v); } } } return d[s]!=-1; } void Init(){//佇列初始化 for(int i=h[s];i;i=e[i].nxt){ int v=e[i].v,w=e[i].w; if(w){ e[i].w=0,e[i^1].w=w; flow[s]-=w,flow[v]+=w; if(v!=s&&v!=t&&!vis[v]){ vis[v]=1; q.push((Node){v,d[v]}); } } } } void Push(int u){//掃描到u點,把u點的流往下推進 for(int i=h[u];i;i=e[i].nxt){ int v=e[i].v,w=e[i].w; if(d[u]==d[v]+1&&w){ int f=min(flow[u],w); e[i].w-=f,e[i^1].w+=f; flow[u]-=f,flow[v]+=f; if(v!=s&&v!=t&&!vis[v]){ vis[v]=1; q.push((Node){v,d[v]}); } } } } void Gap(int u){//斷層優化,把所有點的層數都改為源點+1,使其儘快流回源點 for(int i=1;i<=n*m;++i){ if(i!=s&&i!=t&&d[i]>d[u]&&d[i]<=n) d[i]=n+1; } } void Relabel(int u){//把u點的層設為它連向的點中最小的層數+1,保證能流 d[u]=INF; for(int i=h[u];i;i=e[i].nxt){ int v=e[i].v,w=e[i].w; if(w) d[u]=min(d[u],d[v]); } d[u]++; } int HLPP(){ if(!bfs()) return 0; memset(gap,0,sizeof(gap)); memset(flow,0,sizeof(flow)); memset(vis,0,sizeof(vis)); d[s]=n; for(int i=1;i<=n*m;++i){ if(d[i]!=-1) gap[d[i]]++; } Init(); while(!q.empty()){ int u=q.top().u; q.pop(); vis[u]=0; Push(u); if(flow[u]){ gap[d[u]]--; if(!gap[d[u]]) Gap(u); Relabel(u); gap[d[u]]++; vis[u]=1; q.push((Node){u,d[u]}); } } return flow[t];//最終推到匯點的就是最大流 } int main(){ scanf("%d%d",&n,&m);s=n*m+1,t=n*m+2; for(int i=1;i<=n;++i){ for(int j=1,w;j<=m;++j){ bool ok=0; scanf("%d",&w); sum+=w;//總和 int k=getnum(i,j); //構造二分圖 if((i+j)%2) add(s,k,w),ok=1;//一個集連向源點 else add(k,t,w);//另一個集連向匯點 if(ok){ for(int q=0;q<4;++q){ int ex=i+dx[q],ey=j+dy[q]; if(check(ex,ey)){ int p=getnum(ex,ey); if((ex+ey)%2) swap(k,p); add(k,p,INF);//相鄰點連邊,注意順序啊! } } } } } int ans=sum-HLPP();//我使用的是HLPP預留推進來做這個題的,沒學過的請右轉板子題P4722 printf("%d\n",ans); return 0; }
初學HLPP,板子不好看,請各位巨佬神犇見諒。