做这道题,自己先是想了好几种找被割的边的方法——都被否决了。
后来发现是最小割:只要一条边的两端在不同的点集里面就代表是被割掉的满流边(这些满流边的流量和等于最大流的流量与最小割的权值和)。
但是之前自己想了一个例子,
10 11 1 4 3 4 3 3 3 2 3 4 5 100 5 6 100 6 7 100 7 2 100 1 8 100 8 9 100 9 10 100 10 3 100 0 0
首先这个例子我自己误判了,以为最大流的流量是6,只要割掉1-4和3-2就行——就是脑子短路了,所以我还反过来怀疑双向边,再怀疑DFS和BFS寻找割集的方法,最后居然怀疑最大流和最小割的关系。
后来用了别人的代码,发现我自己解出来的最大流量值错了;但是用自己编的数据(在上面的基础上修改的):
10 11 1 4 3 4 3 3 3 2 3 4 5 3 5 6 100 6 7 100 7 2 100 1 8 100 8 9 100 9 10 100 10 3 100 0 0
她居然给我跑出来了三条容量为3的边,这就是为什么我怀疑最大流和最小割的关系的原因,后来发现人家在寻找源点能到的点集的时候,不仅要求边的剩余流量大于0,还要求基础的cap容量要大于0,这样的做法是错的。修改之后就好了。
AC代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//http://www.renfei.org/blog/isap.html 带解释的ISAP //https://www.cnblogs.com/bosswnx/p/10353301.html 形式和我的比较相近的 ISAP #include<queue> #include<cstdio> #include<cstring> using namespace std; #define maxe 2048 //pay 双向边 一共10万条路 双向就是20万 反边就是40万 #define maxv 55 //pay #define maxn 205 //pay #define sc scanf #define pt printf #define rep(i,a,b) for(int i=(a);i<(b);++i) const int inf = 0x3f3f3f3f; int cg,sp,ins; //cg change sp是总流量 ins是加速回溯点 int N,M ,s,t; int q[maxv],fro,rea; typedef struct ed{ int v,nxt,cap; //dis }ed; ed e[maxe]; typedef struct cz{ int x,y; //dis }cz; cz ob[maxe]; int tp; int tot,head[maxv],cur[maxv],vis[maxv],bk[maxv],d[maxv],num[maxv]; // int mi(int a,int b) {return a<b?a:b;} int mx(int a,int b) {return a>b?a:b;} void add(int u,int v,int cap) { e[tot].v=v; e[tot].nxt=head[u]; /*e[tot].dis=dis;*/ e[tot].cap=cap; head[u]=tot++; e[tot].v=u; e[tot].nxt=head[v]; /*e[tot].dis=-dis;*/ e[tot].cap=0; head[v]=tot++; } // 仅有一次的BFS为ISAP节省了不少时间 bool bfs() { //数组模拟queue memset(vis, 0, sizeof(vis)); fro = rea = 0; q[rea] = t; ++rea; vis[t] = 1; d[t] = 0; int u,v,i; while (rea>fro) { u = q[fro]; ++fro; for (i=head[u]; i!=-1; i=e[i].nxt) { v=e[i].v; if (!vis[v] && e[i^1].cap ) { vis[v] = true; d[v] = d[u] + 1; q[rea] = v; ++rea; } } } return vis[s]; } // 增广 int augment() { int flow = inf, i; cg = t; // 从汇点到源点通过 p 追踪增广路径, flow 为一路上最小的残量 while (cg != s) { i = bk[cg]; if(flow>=e[i].cap) { flow = e[i].cap; ins = e[i^1].v; //用来加速寻找,在最小流量断开的地方重新开始寻找 //嗯,等一下 我这个是从终点往起点寻找,而确定增光路径是从起点到终点 //那么起点是河流的上游,那么回溯的河段应该尽可能的往上游靠近 //所以应该将flow>e[i].cap的大于号改成大于等于号 } cg = e[i^1].v; } cg = t; // 从汇点到源点更新流量 while (cg != s) { i = bk[cg]; e[i].cap -= flow; e[i^1].cap += flow; cg = e[i^1].v; } return flow; } //由于每次修改层次的时候,都是在到剩下子节点的距离中挑选最短的加1 所以层次分明不会出现死循环 int max_flow() { int flow = 0,i,u,v; bool advanced; if(bfs()==false) return 0; memset(num, 0, sizeof(num)); for (i = 1; i <= N; ++i) ++num[d[i]]; //不一定是从s到t,你要知道统计每个层次的点的个数是全局统计的 u = s; memcpy(cur, head, sizeof(head)); while (d[s] < N) //层次问题!!! 一定要保证t是最大的 //终点是0,那么起点所在层次最多是N-1 同理,不是d[s]<t { if (u == t) { flow += augment(); u = ins; //pay speed up } advanced = false; for (i = cur[u]; i!=-1; i=e[i].nxt) { v = e[i].v; if (e[i].cap && d[u] == d[v] + 1) { advanced = true; bk[v] = i; cur[u] = i; u = v; break; } } if (!advanced) { // retreat int m = N; //层次问题!!! 一定要保证t是最大的 for (i = head[u]; i != -1; i=e[i].nxt) { if (e[i].cap&&m>d[e[i].v]) { cur[u] = i; m = d[e[i].v]; } } if (--num[d[u]] == 0) break; // gap 优化 ++num[d[u] = m+1]; //我以前一直在想 如果没有找到怎么办呢 现在发现原来找不到的话距离会被赋成N+1 if (u != s) u = e[bk[u]^1].v; } } return flow; } void init() { tot=0; memset(head,-1,sizeof(head)); //pay } int main() { freopen("in.txt","r",stdin); s=1, t=2,bk[0]=-1; while(~sc("%d%d",&N,&M)&&N&&M) { tp=0; sp = 0; int i,w; init(); for(i=1;i<=M;++i) { sc("%d%d%d",&ob[tp].x,&ob[tp].y,&w); add(ob[tp].x,ob[tp].y,w); add(ob[tp].y,ob[tp].x,w); ++tp; } sp = max_flow(); //pt("%d ",sp); memset(vis, 0, sizeof(vis)); fro = rea = 0; q[rea] = s; ++rea; vis[s] = 1; int u,v; while (rea>fro) { u = q[fro]; ++fro; for (i=head[u]; i!=-1; i=e[i].nxt) { v=e[i].v; if (!vis[v] && e[i].cap ) { vis[v] = true; q[rea] = v; ++rea; } } } for(i=0;i<tp;++i) if(vis[ob[i].x]+vis[ob[i].y]==1) pt("%d %d ",ob[i].x,ob[i].y); pt(" "); } return 0; }