题目大意:你有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;
}