poj 3204 Ikki's Story I - Road Reconstruction
最大流最小割
分析都在代码注释中
/* 题意:对于一个有向图的最大流,在每条流路中,只能修改一条边,使得修改后,整个网络的最大流可以增大,问有多少条这样的边 注意一点,在一条流路中只能修改一条,好像s->1->2->t,假设每条边的容量都是2,那么最大流的流路只有一条,但是这条流路中, 要想增大一条边的容量而使整个网络的最大流增加是不行的,一定要把所有边容量都增大,这与题意不符,所以这个case输出是0,表示1条都没有 首先我们运行一次最大流,得到最终的残余网络,那么我们怎么找到我们要的割边呢?首先割边一定已经是满流的了,假设我们得到一条满流的边u->v 如果源点s可以到达点u,且点v能到达汇点t的话,那么这条满流边就是我们要找的,那么什么是能到达呢? 原来的有向边中,只有非满流的边才能继续走,满流的边我们看做已经是断开的不能再走(即按残余网络来遍历) 所以我们从源点s出发遍历一次整个图,去到的点染色为1。然后从汇点t出发遍历一次整个图,去到的点染色为2 因为要从汇点反过来遍历整个图,所以我们被迫要另外建一个逆邻接表,同样的,我们就算从源点开始遍历,而原始的邻接表有太多无用的信息 (每条有向边还带出一条反边,这些都是没用的信息),所以我们干脆重新建一个顺邻接表,其实就是把原邻接表有用的信息拿出来单独保存 后来拿出来的顺邻接表放在order[]数组中,而逆邻接表放在reorder[]数组中 而我们最后要检查所有满流的边,看两个顶点是不是分别染色为1,2,所以我们开辟一个队列来专门保存这些满流的边,在full队列中 */ #include <cstdio> #include <cstring> #include <vector> #include <queue> using namespace std; #define N 510 #define M 5010 #define INF 0x3f3f3f3f #define min(a,b) a<b?a:b int nume; struct edge { int u,v,cap,flow,next; }e[10*M]; int first[N],n,m; int col[N]; //点染色 vector<int>order[N],reorder[N]; //顺邻接表,逆邻接表,保存不满流的边 queue<int>full; //保存满流的边(边的编号) void add(int u ,int v ,int cap) { e[nume].u=u; e[nume].v=v; e[nume].cap=cap; e[nume].flow=0; e[nume].next=first[u]; first[u]=nume++; } void build() { memset(first,-1,sizeof(first)); nume=0; scanf("%d%d",&n,&m); for(int i=1; i<=m; i++) { int u,v,cap; scanf("%d%d%d",&u,&v,&cap); if(!cap) continue; add(u,v,cap); add(v,u,0); } } void EK(int s, int t) { queue<int>q; int a[N],p[N],FLOW=0; while(1) { memset(a,0,sizeof(a)); a[s]=INF; memset(p,-1,sizeof(p)); q.push(s); while(!q.empty()) { int u,v,cap,flow; u=q.front(); q.pop(); for(int k=first[u]; k!=-1; k=e[k].next) { v=e[k].v; cap=e[k].cap; flow=e[k].flow; if(!a[v] && cap>flow) //没有计算过并且可以增流 { a[v]=min(a[u],cap-flow); p[v]=k; q.push(v); } } } if(!a[t]) break; for(int k=p[t]; k!=-1; k=p[e[k].u]) { e[k].flow += a[t]; e[k^1].flow -= a[t]; } FLOW += a[t]; } } void dfs1(int u ,int c) //从源点出发进行遍历 { col[u]=c; int len=order[u].size(); for(int i=0; i<len; i++) { int v=order[u][i]; if(!col[v]) dfs1(v,c); } } void dfs2(int u ,int c) //从汇点出发逆遍历 { col[u]=c; int len=reorder[u].size(); for(int i=0; i<len; i++) { int v=reorder[u][i]; if(!col[v]) dfs2(v,c); } } void search(int s ,int t) { while(!full.empty()) full.pop(); for(int i=0; i<n; i++) { order[i].clear(); reorder[i].clear(); } for(int k=0; k<nume; k+=2) if(e[k].cap==e[k].flow) //满流的边 full.push(k); else //非满流的边 { int u=e[k].u , v=e[k].v; order[u].push_back(v); //保存顺邻接表 reorder[v].push_back(u); //保存逆邻接表 } memset(col,0,sizeof(col)); dfs1(s,1); dfs2(t,2); int ans=0; while(!full.empty()) //检查所有满流的边 { int x,u,v; x=full.front(); full.pop(); u=e[x].u; v=e[x].v; if(col[u]==1 && col[v]==2) ans++; } printf("%d\n",ans); } int main() { build(); EK(0,n-1); search(0,n-1); return 0; }