同样看了别人的思路,自己写代码。
给你两个整数n和m。
表示公司要开除n个员工,员工之间的关系有m个。
然后给你n个数字,可正可负。
表示开除这个员工所得到(正)或损失(负)的利益。
m 个关系 a b
表示b员工是a员工的下属。
条件:如果开除一个员工,那么必须要开除他的下属,具有传递性(即也要开除下属的下属)。问你公司最大可以获得多少利益,需要开除的人数是多少。
解题报告:
闭合图定义:
它是有向图的一个点集,且这个点集的所有出边仍然指向该点集。
最大权闭合图:
每一个点有一个权值,可正可负,在所有的合法闭合图中,点权之和最大的图就是最大权闭合图。
明显,这道题目就是一个典型的最大权闭合图。
具体推理证明详见国家集训队论文:胡波涛的《最小割模型在信息学竞赛中的应用》
建图:
一个超级源点s,超级汇点t。
s连接所有点权为正的点,容量是点权。
所有点权为负的点连接汇点t,容量的点权乘以-1。
b 是 a的下属,那么连接 a b,容量无穷大。
求出最大流,那么所
有正点权的和 减去 最大流 就是最大权闭合图的最大权,就是公司的最大利益。
在残量网络中从原点s出发,一遍dfs,走还有容量的点,经过的点数就是要开除的人数。
略微证明一下:(证明转自:http://www.answeror.com/archives/27629)
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]}), 注意到括号内的值即为待求的总收益, 所以最小割对应了最大收益, 保证了解的最优性.
Source Code
Problem: 2987 User: 1013101127
Memory: 3176K Time: 2422MS
Language: G++ Result: Accepted
Source Code
#include<iostream>
#include<cstdio>
#include<cstring>
#include<set>
#include<cstdio>
#include<string>
using namespace std;
const long long maxn=5555;
//const int maxm=300006;
const long long inf=0x1fffffff;
struct node
{
long long v,next;
long long val;
}s[6000*34];
long long level[maxn];//顶点的层次
long long p[maxn];
long long que[maxn*10];//BFS中用于遍历的顶点,DFS求增广中记录边
long long out[10*maxn];//DFS用于几乎定点的分支
long long ind;
long long cop_poit[maxn];
node cop_mp[maxn*100*2];
long long topset;
void init()
{
ind=0;
memset(p,-1,sizeof(p));
}
inline void insert(long long x,long long y,long long z)
{
s[ind].v=y;
s[ind].val=z;
s[ind].next=p[x];
p[x]=ind++;
s[ind].v=x;
s[ind].val=0;
s[ind].next=p[y];
p[y]=ind++;
}
long long max_flow(long long n,long long source,long long sink)
{
long long ret=0;
long long h=0,r=0;
while(1)//DFS
{
long long i;
for(i=0;i<=n;++i)
level[i]=0;
h=0,r=0;
level[source]=1;
que[0]=source;
while(h<=r)//BFS
{
long long t=que[h++];
for(i=p[t];i!=-1;i=s[i].next)
{
if(s[i].val&&level[s[i].v]==0)
{
level[s[i].v]=level[t]+1;
que[++r]=s[i].v;
}
}
}
topset=r;//记录原点的集合个数
if(level[sink]==0)break;//找不到汇点
for(i=0;i<=n;++i)
out[i]=p[i];
long long q=-1;
while(1)
{
if(q<0)
{
long long cur=out[source];
for(;cur!=-1;cur=s[cur].next)
{
if(s[cur].val&&out[s[cur].v]!=-1&&level[s[cur].v]==2)
{
break;
}
}
if(cur>=0)
{
que[++q]=cur;
out[source]=s[cur].next;
}
else
{
break;
}
}
long long u=s[que[q]].v;
if(u==sink)//一条增广路
{
long long dd=inf;
long long index=-1;
for(i=0;i<=q;i++)
{
if(dd>s[que[i]].val)
{
dd=s[que[i]].val;
index=i;
}
}
ret+=dd;
//cout<<ret<<endl;
for(i=0;i<=q;i++)
{
s[que[i]].val-=dd;
s[que[i]^1].val+=dd;
}
for(i=0;i<=q;i++)
{
if(s[que[i]].val==0)
{
q=index-1;
break;
}
}
}
else
{
long long cur=out[u];
for(;cur!=-1;cur=s[cur].next)
{
if(s[cur].val&&out[s[cur].v]!=-1&&level[u]+1==level[s[cur].v])
{
break;
}
}
if(cur!=-1)
{
que[++q]=cur;
out[u]=s[cur].next;
}
else
{
out[u]=-1;
q--;
}
}
}
}
return ret;
}
long long m,n;
int main()
{
long long u,v;
long long va[maxn];
long long sum=0;
while(scanf("%d%d",&n,&m)!=EOF)
{
init();
long long start=0;
sum=0;
long long end=n+1;
for(int i=1;i<=n;i++)
{
cin>>va[i];
if(va[i]>=0)
{
sum+=va[i];
insert(start,i,va[i]);
}
else
insert(i,end,-va[i]);
}
for(int i=1;i<=m;i++)
{
cin>>u>>v;
//if(va[u]>0&&va[v]<0)
insert(u,v,inf);
//if(va[u]<0&&va[v]>0)
//insert(v,u,inf);
}
long long ans=max_flow(n+2,start,end);
long long ss=topset;
cout<<ss<<' '<<sum-ans<<endl;
}
return 0;
}