【题意】:公司要炒一些员工的鱿鱼, 若A被炒了, 那A的所有下属也会跟着被炒, 下属关系具有传递性, 且可能构成环, 即A是B的下属, B又间接是A的下属, 炒掉每个人公司会得到一笔收益, 收益可能为负, 问在收益最大的前提下, 最少要炒掉哪些人, 以及最大收益是多少.
【题解】:标准的最大权闭合图,构图:从源点s向每个正收益点连边,容量为收益;从每个负收益点向汇点t连边,容量为收益的相反数;对于i是j的上司,连边i->j,容量为inf。最大收益 = 正收益点权和 - 最小割 = 正收益点权和 - 最大流(胡波涛论文上有证明)。求完最大流后,在残留网络中从s开始dfs,被染色的为S,未被染色的为T,则边集[S,T]为割。这题的关键是如何在最小割的前提下求出最少的割边数目,可以从源点对残量网络进行一次DFS,每个割都会将源汇隔开,所以从源点DFS下去一定会因为碰到某个割而无法前进,用反证法易知这时遍历过的点数就是S集的最少点数。
#include<cstdio>
#include<cstring>
#include<map>
#include<vector>
#include<cmath>
#include<cstdlib>
#include<stack>
#include<queue>
#include <iomanip>
#include<iostream>
#include<algorithm>
using namespace std ;
const int N = 10000 ;
const int M = 150000 ;
const int inf = 1<<30 ;
struct node
{
int u ,v,c,next ;
}edge[M] ;
int head[N],dis[N],gap[N],cur[N],pre[N],vis[N] ;
long long top ,k;
void add(int u ,int v,int c)
{
edge[top].u=u;
edge[top].v=v;
edge[top].c=c;
edge[top].next=head[u];
head[u]=top++;
edge[top].u=v;
edge[top].v=u;
edge[top].c=0;
edge[top].next=head[v];
head[v]=top++;
}
long long sap(int s,int t,int nv)
{
memset(dis,0,sizeof(dis));
memset(gap,0,sizeof(gap));
int u,v,minflow=inf;
long long flow=0 ;
for(int i = 0 ; i <=nv ; i++) cur[i]=head[i] ;
u=pre[s]=s;
gap[s]=nv;
while(dis[s] < nv )
{
loop :
for(int &j=cur[u] ; j!=-1 ; j=edge[j].next)
{
v=edge[j].v ;
if(edge[j].c > 0 && dis[u] == dis[v] +1 )
{
minflow = min(minflow ,edge[j].c) ;
pre[v]=u;
u=v ;
if(v==t)
{
for( u =pre[v] ; v!=s ;v=u,u=pre[u])
{
edge[cur[u]].c -= minflow ;
edge[cur[u]^1].c += minflow ;
}
flow += minflow ;
minflow = inf ;
}
goto loop ;
}
}
int mindis=nv ;
for(int i = head[u] ; i!=-1 ; i=edge[i].next)
{
v=edge[i].v ;
if(edge[i].c > 0 && dis[v] < mindis)
{
mindis = dis[v] ;
cur[u]=i ;
}
}
if(--gap[dis[u]]==0) break ;
gap[ dis[u] = mindis +1 ]++ ;
u=pre[u] ;
}
return flow ;
}
void dfs(int u)
{
vis[u] = 1 ;
for(int i=head[u] ; i!=-1 ; i=edge[i].next)
{
int v= edge[i].v ;
if(!vis[v] && edge[i].c > 0)
{
dfs(v) ;
}
}
}
int main ()
{
int n,m,x,a,b;
while (~scanf("%d%d",&n,&m))
{
top = 0;k=0;
long long num=0;
int s = 0 ,t = n + 1,sum=0;
memset (head,-1,sizeof(head));
for(int i=1;i<=n;i++)
{
scanf("%d",&x);
if(x>0)
{
add(s,i,x);
num+=x;
}
else if(x < 0)
add(i,t,-x);
}
for (int i=0; i<m; i++)
{
scanf("%d%d",&a,&b);
add(a,b,inf);
}
long long ans=num-sap(s,t,t+1);;
memset(vis,0,sizeof(vis));
dfs(s);
for(int i=1;i<=n;i++)
if(vis[i])
sum++;
printf("%d %lld\n",sum,ans);
}
return 0;
}