POJ - 2987 Firing 最大权闭合子图(最小割)

传送门:POJ 2987

题意:老板决定裁员,每开除一个人,同时要将其下属一并开除,如果该下属还有下属,照斩不误。给出每个人的贡献值和从属关系,求最小裁员数及最大贡献值和。

定理:最大权闭合图的的权=原图中权值为正的点权和 - 最小割(最大流)

正解:最大权闭合图定理题,证明及相关建图方法放到代码后面,这题关键是求最小裁员数的部分有意思,由于定理中的相关内容,正常思路是求完最大流后在从源点开始在残余网络上进行搜索(忽略满流边),能到达的点就是最大权闭合图中的点,然而有dalao用了一种特别巧妙的方法叫放大边权法,详见:点击打开链接

仔细想想还是有那么点道理的,因为满流边对应的点我们是不辞退的,而只有满流边才会感知到少了这个1,所以这样我们就能变相计算出要辞退多少个了。

代码:

//ISAP long long
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#define ll long long
#define MAXN 6005
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
int n,m;//题目输入点数,边数
struct Edge{
	int v,next;
	ll cap,flow;
}edge[MAXN*100];
int cur[MAXN],pre[MAXN],gap[MAXN],path[MAXN],dep[MAXN];
int cnt=0;//实际存储总边数 
void init()
{
	cnt=0;
	memset(pre,-1,sizeof(pre)); 
}
void add(int u,int v,ll w,ll rw=0)//加边 单向图三个参数  双向图四个 
{
	edge[cnt].v=v;
	edge[cnt].cap=w;
	edge[cnt].flow=0;
	edge[cnt].next=pre[u];
	pre[u]=cnt++;
	edge[cnt].v=u;
	edge[cnt].cap=rw;
	edge[cnt].flow=0;
	edge[cnt].next=pre[v];
	pre[v]=cnt++;
}
bool bfs(int s,int t)//其实这个bfs可以融合到下面的迭代里,但是好像是时间要长 
{
	memset(dep,-1,sizeof(dep));
	memset(gap,0,sizeof(gap));
	gap[0]=1;
	dep[t]=0;
	queue<int>q;
	while(!q.empty())
	q.pop();
	q.push(t);//从汇点开始反向建层次图 
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		for(int i=pre[u];i!=-1;i=edge[i].next)
		{
			int v=edge[i].v;
			if(dep[v]==-1&&edge[i^1].cap>edge[i^1].flow)//注意是从汇点反向bfs,但应该判断正向弧的余量
			{
				dep[v]=dep[u]+1;
				gap[dep[v]]++;
				q.push(v);
				//if(v==s)//感觉这两句优化加了一般没错,但是有的题可能会错,所以还是注释出来,到时候视情况而定 
				//break;
			}	
		}
	}
	return dep[s]!=-1; 
}
ll isap(int s,int t)
{
	bfs(s,t);
	memcpy(cur,pre,sizeof(pre));
	int u=s;
	path[u]=-1;
	ll ans=0;
	while(dep[s]<n)//迭代寻找增广路 
	{
		if(u==t)
		{
			ll f=inf;
			for(int i=path[u];i!=-1;i=path[edge[i^1].v])//修改找到的增广路 
				f=min(f,edge[i].cap-edge[i].flow);
			for(int i=path[u];i!=-1;i=path[edge[i^1].v])
			{
				edge[i].flow+=f;
				edge[i^1].flow-=f;
			}
			ans+=f;
			u=s;
			continue;
		}
		bool flag=false;
		int v;
		for(int i=cur[u];i!=-1;i=edge[i].next)
		{
			v=edge[i].v;
			if(dep[v]+1==dep[u]&&edge[i].cap-edge[i].flow)
			{
				cur[u]=path[v]=i;//当前弧优化 
				flag=true;
				break;
			}
		}
		if(flag)
		{
			u=v;
			continue;
		}
		int x=n;
		if(!(--gap[dep[u]]))return ans;//gap优化 
		for(int i=pre[u];i!=-1;i=edge[i].next)
		{
			if(edge[i].cap-edge[i].flow&&dep[edge[i].v]<x)
			{
				x=dep[edge[i].v];
				cur[u]=i;//常数优化 
			}
		}
		dep[u]=x+1;
		gap[dep[u]]++;
		if(u!=s)//当前点没有增广路则后退一个点 
		u=edge[path[u]^1].v;
	 } 
	 return ans;
} 
bool vis[MAXN];
void dfs(int u)
{
	vis[u]=1;
	for(int i=pre[u];i!=-1;i=edge[i].next)
	{
		if(edge[i].cap>edge[i].flow&&!vis[edge[i].v])
		dfs(edge[i].v);
	}
}
int main()
{
	int N,M,w,u,v;
	ll sum=0;
	init();
	cin>>N>>M;
	int source=0,sink=N+1;
	n=N;
	for(int i=1;i<=N;i++)
	{
		scanf("%d",&w);
		if(w<0)
		add(i,sink,-w);
		else
		add(source,i,w),sum+=w;
	}
	while(M--)
	{
		scanf("%d%d",&u,&v);
		add(u,v,inf);
	}
	ll ans=isap(source,sink),t=0;
	dfs(0);
	for(int i=1;i<=N;i++)
	if(vis[i])
	t++;
	cout<<t<<" "<<sum-ans;
return 0;
}



请参考胡伯涛的论文《最小割模型在信息学竞赛中的应用》
闭合图的概念就很好引出了。在一个图中,我们选取一些点构成集合,记为V,且集合中的出边(即集合中的点的向外连出的弧),所指向的终点(弧头)也在V中,则我们称V为闭合图。最大权闭合图即在所有闭合图中,集合中点的权值之和最大的V,我们称V为最大权闭合图。


首先引入结论,最小割所产生的两个集合中,其源点S所在集合(除去S)为最大权闭合图,接下来我们来说明一些结论。
证明:最小割为简单割。
        引入一下简单割的概念:割集的每条边都与S或T关联。(请下面阅读时一定分清最小割与简单割,容易混淆)
        那么为什么最小割是简单割呢?因为除S和T之外的点间的边的容量是正无穷,最小割的容量不可能为正无穷。所以,得证。
证明网络中的简单割与原图中闭合图存在一一对应的关系。(即所有闭合图都是简单割,简单割也必定是一个闭合图)。
        证明闭合图是简单割:如果闭合图不是简单割(反证法)。那么说明有一条边是容量为正无穷的边,则说明闭合图中有一条出边的终点不在闭合图中,矛盾。
        证明简单割是闭合图:因为简单割不含正无穷的边,所以不含有连向另一个集合(除T)的点,所以其出边的终点都在简单割中,满足闭合图定义。得正。
证明最小割所产生的两个集合中,其源点S所在集合(除去S)为最大权闭合图。
        首先我们记一个简单割的容量为C,且S所在集合为N,T所在集合为M。
        则C=M中所有权值为正的点的权值(即S与M中点相连的边的容量)+N中所有权值为负的点权值的绝对值(即N中点与T中点相连边的容量)。记(C=x1+y1);(很好理解,不理解画一个图或想象一下就明白了)。
        我们记N这个闭合图的权值和为W。
        则W=N中权值为正的点的权值-N中权值为负的点的权值的绝对值。记(W=x2-y2);
        则W+C=x1+y1+x2-y2。
        因为明显y1=y2,所以W+C=x1+x2;
        x1为M中所有权值为正的点的权值,x2为N中权值为正的点的权值。
        所以x1+x2=所有权值为正的点的权值之和(记为TOT).
        所以我们得到W+C=TOT.整理一下W=TOT-C.
        到这里我们就得到了闭合图的权值与简单割的容量的关系。
        因为TOT为定值,所以我们欲使W最大,即C最小,即此时这个简单割为最小割,此时闭合图为其源点S所在集合(除去S)。得证。


至此,我们就将最大权闭合图问题转化为了求最小割的问题。求最小割用最小割容量=最大流,即可将问题转化为求最大流的问题。
-----------------------------------------------------------

本题还有一个要求就是要求不仅要是最大权,并且要求点数还最少

下面证明最小割对应取点方案就是最小取点数
由于原图是个DAG图,所以对于取得的最大权闭合图K,取它的任意一个子图G,如果从K-G仍然是一个闭合图,那么的G点权和一定大于等于0,例如:1->2,2->3,1->4,4->5,若最大权闭合图为:{1,2,3,4,5},那么其中任一满足条件的G({1},{1,2},{1,2,3},{1,4},{1,4,5})点权和一定大于等于0,否则去除G,K-G仍然为闭合图,但是K-G的点权和会大于K
所以如果有两种取点方式使得权值相同,但是取点数不同的话,那么肯定存在一个可以移除的满足条件的子图G,其点权和为0
下面考虑构造的网络,对于G在网络中的对应图G',由于在网络求的是最小割,即最大流,而且G的点权和为0,所以G'中与源点S连边的容量和等于G'中与汇点T连边的流量和,同时由于去除G后K还是一个闭合图,所以只有可能G'中的流量流入K'-G',不可能有流量从K'-G'流入G',所以G'的边中除了流量为inf的那些,一定是满流的
再考虑在残留网络中求出取点集的方法,从源点开始floodfill,忽略满流边,即残留网络中的0流边,可以遍历到的点就是要取的点集了,这个道理想一下简单割和闭合图的取法一一对应就可以了
那么G'既然是满流的,在残留网络中就不可能对这些0流边进行处理,那就不会取到G中的点进入取点集,所以建立网络求得得最小割对应的取法取出的就是最小的点数了

以上证明转载自:http://blog.csdn.net/sdj222555/article/details/7797534

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值