最小割
什么是最小割?
一个割就是一组边的集合,将给集合边从图中边集合中移除,那么图被分割为两个部分,这两个部分之间没有任何边连接。
如何找到这个最小割?
当一个图被割分成两个部分时,不再存在S到T的通路,所以割的代价必定大于等于图的最大流(这个需要添加额外说明吗?应该不需要吧,算是非常明显的结论了吧)。那么也就是说,割的代码最小不能小于图的最大流,也就是割的代价等于图的最大流。
例题1
题意:每次操作可以让除了一条边之外的所有边-1,问让一条边必定出现在最小生成树上的最小操作数是多少。
思路:
1.由于是最小生成树,只与边权大小有关,操作等价于让本条边加1。
2.让目标边为树的一条边,那么就肯定是让比它边权小的一直加,知道恰好大于它的边权为止。
3.s-t不连通的前提是比它小的边形成的图不能联通,这不就是最小割吗?代价就是w[i]-w[id]+1;
scanf("%d%d%d",&n,&m,&id);
for(int i=1; i<=m; i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
a[i]=u,b[i]=v,c[i]=w;
}
s=a[id],t=b[id];
for(int i=1;i<=m;i++)
{
if(i==id)continue;
if(c[i]>c[id])continue;
add(a[i],b[i],c[id]-c[i]+1);
add(b[i],a[i],c[id]-c[i]+1);
}
printf("%d\n",ac.Dinic());
-
1.最小割点
-
2.最大权闭合子图
1.最小点割集是指:给出一张有向图(无向图)和两个点 ST 每个点都有一个正数点权,求一个不包含点S T 的权值和最小的点集使得删掉点集中的所有点后,S 无法到达T 。
求法:对于这个问题,我们将每一个点拆成两个点,一个为入点,一个为出点,这两个点之间有一条边权为原图中点权的有向边,从入点指向出点。对于原图中的边x→y ,我们转化成出点到入点的连边,x+n→y,y+n→x;
在转化完的图上跑最小割就是原图的最小点割集
scanf("%d%d",&n,&m);
scanf("%d%d",&s,&t);
t=t+n;
for(int i=1;i<=n;i++)
scanf("%d",&val[i]);
for(int i=1;i<=m;i++)
{
int u,v;
scanf("%d%d",&u,&v);
add(u+n,v,INF);
add(v,u+n,0);
add(v+n,u,INF);
add(u,v+n,0);
}
for(int i=1;i<=n;i++)
add(i,i+n,val[i]),add(i+n,i,0);
printf("%d\n",ac.Dinic());
2.最大权闭合子图:有一个有向图,每一个点都有一个权值(可以为正或负或0),选择一个权值和最大的子图,使得每个点的后继都在子图里面,这个子图就叫最大权闭合子图。
能选的子图有Ø,{4},{3,4},{2,4},{1,2,3,4},它们的权值分别为0,-1,5,-6,4.
所以最大权闭合子图为{3,4},权值为5.
结论:最大权闭合子图=正权值和-最小割。
scanf("%d%d",&n,&m);
s=n+m+1,t=n+m+2;
for(int i=1; i<=n; i++)
scanf("%d",&val[i]);
for(int i=1; i<=n; i++)
add(i,t,val[i]),add(t,i,0);
int sum=0;
for(int i=1; i<=m; i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add(n+i,u,INF),add(u,n+i,0);
add(n+i,v,INF),add(v,n+i,0);
add(s,n+i,w),add(n+i,s,0);
sum+=w;
}
printf("%d\n",sum-ac.Dinic());