POJ 2987 Firing 最大权闭合图

题目大意:你有N个员工,其中一部分不好好上班啦,所以你要开除他~~(好悲催...)但是不是所有员工都消极怠工,有些人还是会给公司赚点钱的嘛..你在开除一个员工的同时还必须连同他的下属一起开除..(由此可见跟一个好BOSS多重要..),由此你在开除人的时候就得斟酌一番了。每个员工都有自己的权值,代表开除他会给公司带来的收入(如果是负数的话就代表亏损),问开除哪些人会给公司带来的收益最大,还有在收益最大的时候开除的最少的人数。

解题思路:

1.求最大收益当然是标准的最大权闭合图的模型了。

对于每个员工的权值v[i] , 如果v[i]>0,加一条源点到i,容量为v[i]的边;

如果v[i]<0,加i到汇点,容量为-v[i]的边。

并定义个临时变量sum为所有v[i]为正的和。

对于每个上下级关系x y(y是x的下属), 加一条x->y 容量为INF的边,

求最小割, sum-最小割 的值就为最大的收益了。


2.求开除的最少的人数。

先说方法再证明。

求完最小割以后, 从源点开始, 按残留网络开始遍历 , 能遍历到的点数-1 (减掉源点) 就为最少开除的人数了。

首先证明为什么是能遍历到的点。(建议先看胡波涛的论文,接下来的证明基本上都是引用那篇论文的东西)

假设最小割把图分为源点所在点的集合N和汇点所在点的集合M,

由最小割的定义,从N到M是没有残余流量的,所以从源点遍历不能遍历到M,

而且求完最小割后N中的点即需要开除的员工

{见胡波涛论文 最大权=正权值和-最小割 那部分的证明,

假设N中正点权的权值和为X,负权值和为Y,则这题所求的最大收益为X-Y,

X和Y相关的点即为要开除的点}


接下来证明点数是唯一的,

点数不唯一的时候说明最小割不唯一,

这就说明在寻找增广路上有两个或多个最小值,

但我们在遍历的时候肯定找离源点最近的那个值,

就保证开除的点最少了。

由此得证

贴代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

#define N 5010
#define M 200000
#define INF 1e9
struct
{
    int to,next;
    int c;
}edge[M];
int head[N],level[N],ip;
int que[N];
bool makelevel(int s,int t)
{
    memset(level,0,sizeof(level));
    int iq=0,top;
    que[iq++]=s;
    level[s]=1;
    for(int i=0;i<iq;i++)
    {
        top=que[i];
        if(top==t)  return 1;
        for(int k=head[top];k!=-1;k=edge[k].next)
        {
            if(!level[edge[k].to]&&edge[k].c>0)
            {
                que[iq++]=edge[k].to;
                level[edge[k].to]=level[top]+1;
            }
        }
    }
    return 0;
}
long long  dfs(int now,long long  maxf,int t)
{
    if(now==t)  return maxf;
    long long   ret=0,c;
    for(int k=head[now];k!=-1;k=edge[k].next)
    {
        if(edge[k].c>0&&level[edge[k].to]==(level[now]+1))
        {
            c=dfs(edge[k].to,min(maxf-ret,(long long)edge[k].c),t);
            edge[k].c-=c;
            edge[k^1].c+=c;
            ret+=c;
            if(ret==maxf)   return  ret;
        }
    }
    if(!ret) level[now]-=2;
    return ret;
}
long long   dinic(int s,int t)
{
    long long  ans=0;
    while(makelevel(s,t))   ans+=dfs(s,INF,t);
    return ans;
}
void add(int u,int v,int c,int f) //有向边f为0 ,否则为 c
{
    edge[ip].to=v;edge[ip].c=c;edge[ip].next=head[u];head[u]=ip++;
    edge[ip].to=u;edge[ip].c=f;edge[ip].next=head[v];head[v]=ip++;
}
void dfs1(int pos,int &num) //从源点开始遍历
{
    level[pos]=1; num++;
    for(int p=head[pos];p!=-1;p=edge[p].next)
    if(!level[ edge[p].to ] && edge[p].c>0 ) dfs1(edge[p].to,num);
}
int main()
{
    int n,m,x,y;
    while(cin>>n>>m)
    {
        memset(head,-1,sizeof(head)); ip=0;
        long long  sum=0;
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&x);
            if(x>0)
            {
                 add(0,i,x,0);
                 sum+=x; //求权值大于0的点权的和
            }
            else if(x<0) add(i,n+1,-x,0);
        }
        while(m--)
        {
            scanf("%d%d",&x,&y);
            add(x,y,INF,0);
        }
        long long ans=sum-dinic(0,n+1); //最大收益=权值大于0的权值的和 - 最小割
        memset(level,0,sizeof(level));
        int num=-1;
        dfs1(0,num); //遍历从源点开始遍历到的点的数目
        cout<<num<<' '<<ans<<endl;
    }
    return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值