网络流经典模型:最大权闭合子图和最大密度子图(坑)

前言

前文申明:此博客主题内容是《胡伯涛: 最小割模型在信息学竞赛中的应用》中的部分内容。

但是因为此资料是本地资料,不知道原文地址在哪,且不知道怎么联系胡伯涛奆佬,所以标的是原创,还请谅解。

其实网络流还有不少技巧,比如说什么拆点拆边什么啦,但是都是一些比较小的技巧,等以后无聊了再统一的整理一下。

这里介绍两个网络流比较NB的模型。(觉得NB可能是因为我菜吧)

最大权闭合子图

例题

[六省联考2017]寿司餐厅

链接

当时就想着用DP搞,结果死活搞不出那个m=1的做法

最大权闭合子图介绍

给你新的一道题目,有\(n\)个点,每个点有个\(a\)值,选了就会加上其\(a\)值,那么很明显加上全部正的\(a\)即可。

但是现在要求给你一些关系:选了\(x\)必须选\(y\)

我们现在的\(ans\)先加上所有的正数。

那么,考虑最小割,如果在最小割中割了\(x\)\(s\)的边,表示不选\(x\),而割了\(x\)\(t\)的边,然后需要注意的是,边权不是价值,而是代价,也就是需要从\(ans\)中减去的东西。(当然,倒过来应该也没有问题。)

对于一个点\(i\),如果\(a_i>0\),则向\(S\)连边权为\(a_i\)的边,向\(t\)连边权为\(0\)的边,如果\(a_i≤0\),则向\(S\)连边权为\(0\)的边,向\(T\)连边权为\(-a_i\)的边。

好,那么如果选了\(x\)必须选\(y\),就把\(x\)\(y\)连一条边,边权为\(∞\),为什么?

因为如果\(x\)选了,割了与\(t\)的边,\(y\)不选,割了与\(s\)的边,那么就一定存在一条路径:\(s->x->y->t\)

所以\(x\)选了,\(y\)必须被选。

这就是最大权闭合子图。

做法

仔细一看,对于\(d_{i,j}\)而言,选了其必须选择\(d_{i+1,j},d_{i,j-1}\),而对于\(d_{i,i}\)而言,选了其必须删去\(i\)的代号,而对于\(i\),如果其被选择,那么也要删去其代号的平方乘\(m\)

然后跑Dinic即可。

代码

时间复杂度:\(O(MaxFlow(n,n+m))\)

#include<cstdio>
#include<cstring>
#define  N  110
#define  NN  12000
#define  M  110000
using  namespace  std;
inline  int  mymin(int  x,int  y){return  x<y?x:y;}
struct  node
{
	int  y,next,c;
}a[M];int  len=1,last[NN];
inline  void  ins_node(int  x,int  y,int  c){len++;a[len].y=y;a[len].c=c;a[len].next=last[x];last[x]=len;}
inline  void  ins(int  x,int  y,int  c){ins_node(x,y,c);ins_node(y,x,0);}
int  list[NN],head,tail,h[NN],st,ed;
bool  bfs()
{
	memset(h,0,sizeof(h));h[ed]=1;
	head=1;tail=1;list[1]=ed;
	while(head<=tail)
	{
		int  x=list[head++];
		for(int  k=last[x];k;k=a[k].next)
		{
			int  y=a[k].y;
			if(a[k^1].c  &&  !h[y])
			{
				h[y]=h[x]+1;
				list[++tail]=y;
			}
		}
	}
	return  h[st];
}
int  dinic(int  x,int  f)
{
	if(x==ed)return  f;
	int  s=0,t;
	for(int  k=last[x];k;k=a[k].next)
	{
		int  y=a[k].y;
		if(a[k].c  &&  h[x]==h[y]+1)
		{
			s+=t=dinic(y,mymin(f-s,a[k].c));
			a[k].c-=t;a[k^1].c+=t;
			if(s==f)return  s;
		}
	}
	h[x]=0;
	return  s;
}
bool  v[1100];
int  ans=0;
inline  void  jian(int  x,int  v)
{
	if(v>0)
	{
		ans+=v;
		ins(st,x,v);
	}
	else  if(v<0)ins(x,ed,-v);
}
int  n,m,d[N][N];
int  main()
{
	scanf("%d%d",&n,&m);
	st=n*n+n+1000+1;ed=st+1;
	for(int  i=1;i<=n;i++)
	{
		int  x;scanf("%d",&x);
		if(!v[x]  &&  m)jian(n*n+x,-m*x*x),v[x]=1;
		jian(n*n+1000+i,-x);
		ins(n*n+1000+i,n*n+x,999999999);
	}
	for(int  i=1;i<=n;i++)
	{
		for(int  j=i;j<=n;j++)
		{
			scanf("%d",&d[i][j]);
			jian((i-1)*n+j,d[i][j]);
		}
	}
	for(int  i=1;i<=n;i++)
	{
		for(int  j=i;j<=n;j++)
		{
			if(i==j)ins((i-1)*n+j,n*n+1000+i,999999999);
			else  ins((i-1)*n+j,i*n+j,999999999),ins((i-1)*n+j,(i-1)*n+j-1,999999999);
		}
	}
	while(bfs())
	{
		ans-=dinic(st,999999999);
	}
	printf("%d\n",ans);
	return  0;
}
/*
1-n^2表示d(i,j)
n^2+1-n^2+1000表示第i个代号。
n^2+1001-n^2+1000+n表示第i个数字,减去其代号 
*/

证明

如果从感性的角度上理解的话,就是利用最小割处理了一波最小化问题,花最小的代价使得我们选出的闭合图合法。

现在将问题简化,有\(n\)个点,\(d_{i}\)为第\(i\)个点的权值,一个有向图\(G=(V,E)\),其中边\((u,v)\)表示选了\(u\)必须选\(v\)

我们的模型是:

\(c(s,i)=d_{i}(d_i>0)\)
\(c(i,t)=-d_{i}(d_i<0)\)
\(c(i,j)=inf((i,j)∈ E)\)

当然,推导的话是直接照搬胡伯涛论文中的内容,因为他写的确实很好。

在这里插入图片描述在这里插入图片描述在这里插入图片描述

最大密度子图

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
其实感觉点边均带权和不带权的推导式差不多的。

当然,至于不带权的边权图关于其中的精度问题我是有个想法的,因为也说了,精度是在\(\frac{1}{n^2}\)的,所以其实可以把二分精度设在\(\frac{1}{2n^2}\),然后再结束之后\(O(nm)\)把所有分母的取值枚举一一遍,如果误差在\(\frac{1}{2n^2}\)以内,则就为这个值。(实际上可以用\(O(n)\)或者各种优化加速此过程,反正我目前想到的最快的是\(O(n)\),暂时想不到更快的了。)

参考文献

你谷题解

《胡伯涛: 最小割模型在信息学竞赛中的应用》

补充例题。

加深对这个的学习。

posted @ 2021-03-02 22:04  敌敌畏58  阅读(145)  评论(0编辑  收藏  举报