最小割树小记
Posted yllcm
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了最小割树小记相关的知识,希望对你有一定的参考价值。
最小割树是一种支持动态查询两点间最小割的结构。
构造
任意选两个点 \\(s,t\\),在全图上跑出 \\(s\\to t\\) 的最小割 \\(w\\),建立边 \\((s,t,w)\\)。设残量网络上与 \\(s\\) 连通的部分为 \\(S\\),与 \\(t\\) 连通的部分为 \\(T\\),则将图分成 \\(S,T\\) 两个点集,并在两个点集上做类似的事情,直到点集只剩下一个点。
此时 \\(u,v\\) 的最小割为树上路径最小值。
证明
引理 \\(1\\):若 \\(u,v\\) 分别在 \\(S,T\\) 内,则 \\(\\textcut(u,v)\\leq \\textcut(s,t)\\)。
容易发现 \\(s\\to t\\) 的割事实上构造了 \\(u\\to v\\) 的割的上界。
引理 \\(2\\):任意选三个点 \\(a,b,c\\),则 \\(|\\\\textcut(a,b),\\textcut(b,c),\\textcut(a,c)\\|\\leq 2\\)。
若三个互不相同,则取最大的,不妨设为 \\(\\textcut(a,b)\\),则 \\(a,b\\) 在 \\(\\textcut(a,c),\\textcut(b,c)\\) 必然位于同侧,那么后两者显然相同,否则可以调整。
推论:\\(\\textcut(a,b)\\geq\\min(\\texta,c,\\textb,c)\\)。多元情况:\\(\\textcut(x_1,x_k)\\geq \\min_i=1^k\\textcut(x_i,x_i+1)\\)。
回到原问题,取路径上两点 \\(u,v\\),考察最小的边 \\((x,y)\\),显然 \\(u,v\\) 位于 \\(x,y\\) 最小割异侧,故 \\(\\textcut(u,v)\\leq\\textcut(x,y)\\),根据推论有 \\(\\textcut(u,v)\\geq \\textcut(x,y)\\),QED。
本质地,最小割树依赖于一个事实:在某次最小割之后,处于残量网络上同一连通块的两点之间最小割必定位于这个连通块内部,而这构成了一个树的结构。
[bzoj2229][Zjoi2011]最小割_网络流_最小割树
最小割 bzoj-2229 Zjoi-2011
题目大意:题目链接。
注释:略。
想法:
在这里给出最小割树的定义。
最小割树啊,就是这样一棵树。一个图的最小割树满足这棵树上任意两点之间的最小值就是原图中这两点之间的最小割。
这个性质显然是非常优秀的。
我们不妨这样假设,我么已经把最小割树求出来了,那么这个题就迎刃而解了。
我们可以直接枚举点对,然后暴力验证就可以直接枚举出所有的合法点对是吧。
那么问题来了,我们如何才能求出所有的合法的点对?
这就需要用到了最小割树的构建过程。
我们最小割树的构建方式是分治构建的。
也就是说:
我们每次直接随意取出两个点然后在原图中求出这两个点的最小割。
并且在这两个点之间连一条等于最小割大小的边。
之后我们对于原图把所有和S相连的分到一侧,把所有和T相连的分到另一侧。
递归分治即可。
Code:
#include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include<algorithm> #include<queue> #define inf 0x3f3f3f3f #define N 155 using namespace std; int cnt,n,m,dis[N],last[N],a[N],tmp[N],ans[N][N],s,t,mark[N]; struct edge{int to,c,next;}e[N*200]; queue <int> q; void addedge(int u,int v,int c) { e[++cnt].to=v;e[cnt].c=c;e[cnt].next=last[u];last[u]=cnt; e[++cnt].to=u;e[cnt].c=c;e[cnt].next=last[v];last[v]=cnt; } bool bfs() { memset(dis,0,sizeof(dis)); dis[s]=2; while (!q.empty()) q.pop(); q.push(s); while (!q.empty()) { int u=q.front(); q.pop(); for (int i=last[u];i;i=e[i].next) if (e[i].c&&!dis[e[i].to]) { dis[e[i].to]=dis[u]+1; if (e[i].to==t) return 1; q.push(e[i].to); } } return 0; } int dfs(int x,int maxf) { if (x==t||!maxf) return maxf; int ret=0; for (int i=last[x];i;i=e[i].next) if (e[i].c&&dis[e[i].to]==dis[x]+1) { int f=dfs(e[i].to,min(e[i].c,maxf-ret)); e[i].c-=f; e[i^1].c+=f; ret+=f; if (ret==maxf) break; } if (!ret) dis[x]=0; return ret; } void dfs(int x) { mark[x]=1; for (int i=last[x];i;i=e[i].next) if (e[i].c&&!mark[e[i].to]) dfs(e[i].to); } void solve(int l,int r) { if (l==r) return; s=a[l];t=a[r]; for (int i=2;i<=cnt;i+=2) e[i].c=e[i^1].c=(e[i].c+e[i^1].c)/2; int flow=0; while (bfs()) flow+=dfs(s,inf); memset(mark,0,sizeof(mark)); dfs(s); for (int i=1;i<=n;i++) if (mark[i]) for (int j=1;j<=n;j++) if (!mark[j]) ans[i][j]=ans[j][i]=min(ans[i][j],flow); int i=l,j=r; for (int k=l;k<=r;k++) if (mark[a[k]]) tmp[i++]=a[k]; else tmp[j--]=a[k]; for (int k=l;k<=r;k++) a[k]=tmp[k]; solve(l,i-1); solve(j+1,r); } int main() { int cas; scanf("%d",&cas); while (cas--) { scanf("%d%d",&n,&m); cnt=1; for (int i=1;i<=n;i++) a[i]=i; memset(last,0,sizeof(last)); memset(ans,inf,sizeof(ans)); for (int i=1;i<=m;i++) { int x,y,z; scanf("%d%d%d",&x,&y,&z); addedge(x,y,z); } solve(1,n); int q; scanf("%d",&q); for (int i=1;i<=q;i++) { int x,tot=0; scanf("%d",&x); for (int i=1;i<n;i++) for (int j=i+1;j<=n;j++) if (ans[i][j]<=x) tot++; printf("%d ",tot); } cout<<endl; } return 0; }
小结:好东西啊。
以上是关于最小割树小记的主要内容,如果未能解决你的问题,请参考以下文章