poj 2978 最大权闭图

在一个图中,我们选取一些点构成集合,记为V,且集合中的出边(即集合中的点的向外连出的弧),所指向的终点(弧头)也在V中,则我们称V为闭合图。最大权闭合图即在所有闭合图中,集合中点的权值之和最大的V,我们称V为最大权闭合图。

 

 

上图中闭合图有

     {5}、{2,5}、{4,5}

     {2,4,5}、{3,4,5}

     {1,2,3,4,5}、{1,2,4,5}

最大权闭合图为{3,4,5}。

 

solution: 建图求最小割, 从源点对残留网络做DFS, 得到最小的点集合. 以人为顶点, A是B的上司, 就从A往B连一条权值无穷大的弧, 加一个源点, 一个汇点, 源点往每个正收益的顶点连一条权值为此收益的弧, 每个负收益的顶点往汇点连一条权值为负收益的绝对值的弧, 然后求出最大流f, 总的正收益减去f就是最大收益了.

黑书上的对于这种构图法的正确性的证明极其简单, 直观, 主要分为以下两步:

1. 因为不连接源汇的边的权值都是无穷大, 所以最小割一定是简单割, 最终被删除的点集即为去掉割以后源点s所在的集合, 记为S, 即若删除了v, 则v的所有后继都会被删除, 这就满足了原问题的唯一约束条件, 保证了解的正确性.

2. 设正收益为b[i], 负收益的绝对值为c[i]. 对于任何一个负收益的顶点v[i]如果在S集合中, 说明v[i]要被删除, 此时容量c[i]被计算在切割中; 对于任何一个正收益的顶点v[i]如果在S集合中, 说明v[i]要被删除, 此时容量b[i]未被计算在切割中. 设正收益之和为B, 则B-sum{b[i]}+sum{c[j]}即为最小割的值(其中i为S中的所有正权点, j为S中的所有负权点), 等价于B-(sum{b[i]}-sum{c[j]}), 注意到括号内的值即为待求的总收益, 所以最小割对应了最大收益, 保证了解的最优性.

即最大权闭合图权值=所有正权值点的权值和-最小割,并且最小割中去除源点为点数最少的最大权值闭合图。

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<algorithm>
using namespace std;
const int MAXN=5020;
const int EM=200005;
const long long INF=((long long)1<<63-1);
int first[MAXN],dist[MAXN],size,vis[MAXN];
int Q[MAXN];
int s,t,N,M;
struct Edge
{
	int v,next;
	long long c;
}edge[EM*5];
void insert(int u,int v,long long c)
{
	edge[size].v=v;
	edge[size].c=c;
	edge[size].next=first[u];
	first[u]=size++;
}
int dinic_bfs()
{
	int head=0,tail=0;
	memset(dist,0,sizeof(dist));
	dist[s]=1; Q[tail++]=s;
	while(head<tail)
	{
		int u=Q[head++],v;
		for(int e=first[u];e!=-1;e=edge[e].next)
		{
			if(edge[e].c && !dist[v=edge[e].v])
			{
				dist[v]=dist[u]+1;
				if(v==t) return 1;
				Q[tail++]=v;
			}
		}
	}
	return 0;
}
long long dinic_dfs(int u,long long cap)
{
	long long ret=0; int v;
	if(u==t) return cap;
	for(int e=first[u];e!=-1;e=edge[e].next)
	{
		if(edge[e].c && dist[v=edge[e].v]==dist[u]+1 && ret<cap)
		{
			long long flow=dinic_dfs(v,min(cap-ret,edge[e].c));
			if(flow>0)
			{
				edge[e].c-=flow;
				edge[e^1].c+=flow;
				ret+=flow;
			}
		}
	}
	if(!ret) dist[u]=-1;
	return ret;
}
long long dinic()
{
	long long ans=0,tmp;
	while(dinic_bfs())
		while(tmp=dinic_dfs(s,INF)) ans+=tmp;
	return ans;
}
void dfs(int u)
{
    vis[u]=1;
    for(int e=first[u];e!=-1;e=edge[e].next)
        if(edge[e].c>0 && !vis[edge[e].v]) dfs(edge[e].v);
}
int main()
{
//    freopen("test.txt","r",stdin);
    scanf("%d%d",&N,&M);
    s=0;t=N+1;
    long long sum=0;
    memset(first,-1,sizeof(first));size=0;
    for(int i=1;i<=N;i++)
    {
        long long a;
        scanf("%lld",&a);
        if(a>0)
        {
            sum+=a;insert(0,i,a);insert(i,0,0);
        }
        else
        {
            insert(i,t,-a);insert(t,i,0);
        }
    }
    for(int i=0;i<M;i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        insert(u,v,INF);insert(v,u,0);
    }
    long long din=dinic();
    memset(vis,0,sizeof(vis));int cnt=0;
    dfs(0);
    for(int i=1;i<=N;i++)
        if(vis[i]) cnt++;
    printf("%d %lld\n",cnt,sum-din);
    return 0;
}














 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值