網路流(最小割最大流(記錄路徑))【POJ1815】
阿新 • • 發佈:2019-01-01
【POJ1815】
出處:原帖
題意:就是求s點到t點,最少去掉幾個點使得他們不連通。如果無解輸出NO ANSWER!
解題思路 因為最小割只能求割掉幾條邊的解,我們要求的是割掉幾個點。那麼我們可以這樣考慮:把每個點拆成入點和出點。入點->出點權值為1。那麼割掉這條邊就相當於割掉這個點了,就能把這題轉化成最小割。那麼原來的邊,我們是不希望去割它的,所以我們將原來的邊的權值設定為INF。例如,原來邊為u->v那麼就變成out(u)->in(v)。這樣跑一次最大流,跑出來的答案就是最小割掉的點數。這題還要我們求出一個點集,就是割掉的點,按字典序排列。因為割點可能會有多種組合,所以最簡單的思路就是列舉。如果這個點割掉之後,跑出來的答案能少割一個點,那麼這個點就是要求的點之一,割掉它,繼續列舉,一直到枚舉出所有答案為止。
應該還有不列舉的更好方法,求教學。。。
程式碼:
#include<cstdio> #include<cstring> #include<string> #include<queue> #include<map> #include<iostream> #include<vector> #include<cstdlib> #include<cmath> #include<stack> #include<cctype> #include<set> #include<ctime> #include<cassert> #include<algorithm> using namespace std; typedef long long ll; const int INF=1e9+7; typedef pair<int,int> pii; #define MP make_pair #define PB push_back #define in(x) (x) #define out(x) (x+n) int n,st,ed; int start[210][210],pic[555][555],d[555]; bool del[555]; vector<int> pr; void build(){ for(int i=1;i<=n;i++){ if(!del[i]) pic[in(i)][out(i)]=1;//拆點,後面枚舉了割掉的點,所以用del陣列標記 //容量是1,希望在割邊的時候割它,割掉點中的邊相當於割點,就把最小割轉化成割點了 } for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++)if(start[i][j]){ pic[out(i)][in(j)]=INF; //割這條邊的流量是INF,意味著不希望去割它 } } } bool BFS(){ queue<int> Q; memset(d,-1,sizeof d); Q.push(st);d[st]=0; while(!Q.empty()){ int s=Q.front();Q.pop(); for(int i=1;i<=n+n;i++){ if(pic[s][i]>0&&d[i]<0){ d[i]=d[s]+1;if(i==ed)return true; Q.push(i); } } } return false; } int DFS(int s,int t,int flow){ if(s==t||flow==0)return flow; int ans=0; for(int i=1;i<=n+n;i++){ if(d[i]==d[s]+1&&pic[s][i]>0){ int ff=DFS(i,t,min(pic[s][i],flow)); if(ff>0){ pic[s][i]-=ff; pic[i][s]+=ff; ans+=ff; flow-=ff; if(!flow)break; } } } if(!ans)d[s]=-1; return ans; } int dinic(){ int ans=0; while(BFS()){ ans+=DFS(st,ed,INF); } return ans; } int main() { // freopen("D://input.txt","r",stdin); scanf("%d%d%d",&n,&st,&ed); for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ scanf("%d",&start[i][j]); } } if(start[st][ed]){printf("NO ANSWER!\n");return 0;} //只有直接相連的時候會無解 st=out(st);ed=in(ed); build(); int ans=dinic(); printf("%d\n",ans); if(!ans)return 0; for(int i=1;i<=n;i++){ //因為割點可能會有很多組合,而且要求字典序最小,所以只能割一個就標記上,一直到割完為止 if(i==st-n||i==ed)continue; del[i]=true; build(); int t=dinic(); if(t<ans){ ans--;pr.PB(i); if(ans==0)break; } else del[i]=false;; } for(int i=0;i<(int)pr.size();i++){ if(i)printf(" "); printf("%d",pr[i]); } printf("\n"); return 0; }