您的位置:首页 > 其它

POJ 2987 Firing 最大权闭合图

2013-06-18 16:02 375 查看
题目大意:你有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
,level
,ip;
int que
;
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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: