在一个图中,我们选取一些点构成集合,记为V,且集合中的出边(即集合中的点的向外连出的弧),所指向的终点(弧头)也在V中,则我们称V为闭合图。最大权闭合图即在所有闭合图中,集合中点的权值之和最大的V,我们称V为最大权闭合图。
上图中闭合图有
{5}、{2,5}、{4,5}
{2,4,5}、{3,4,5}
{1,2,3,4,5}、{1,2,4,5}
最大权闭合图为{3,4,5}。
solution: 建图求最小割, 从源点对残留网络做DFS, 得到最小的点集合. 以人为顶点, A是B的上司, 就从A往B连一条权值无穷大的弧, 加一个源点, 一个汇点, 源点往每个正收益的顶点连一条权值为此收益的弧, 每个负收益的顶点往汇点连一条权值为负收益的绝对值的弧, 然后求出最大流f, 总的正收益减去f就是最大收益了.
黑书上的对于这种构图法的正确性的证明极其简单, 直观, 主要分为以下两步:
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]}), 注意到括号内的值即为待求的总收益, 所以最小割对应了最大收益, 保证了解的最优性.
即最大权闭合图权值=所有正权值点的权值和-最小割,并且最小割中去除源点为点数最少的最大权值闭合图。
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<algorithm>
using namespace std;
const int MAXN=5020;
const int EM=200005;
const long long INF=((long long)1<<63-1);
int first[MAXN],dist[MAXN],size,vis[MAXN];
int Q[MAXN];
int s,t,N,M;
struct Edge
{
int v,next;
long long c;
}edge[EM*5];
void insert(int u,int v,long long c)
{
edge[size].v=v;
edge[size].c=c;
edge[size].next=first[u];
first[u]=size++;
}
int dinic_bfs()
{
int head=0,tail=0;
memset(dist,0,sizeof(dist));
dist[s]=1; Q[tail++]=s;
while(head<tail)
{
int u=Q[head++],v;
for(int e=first[u];e!=-1;e=edge[e].next)
{
if(edge[e].c && !dist[v=edge[e].v])
{
dist[v]=dist[u]+1;
if(v==t) return 1;
Q[tail++]=v;
}
}
}
return 0;
}
long long dinic_dfs(int u,long long cap)
{
long long ret=0; int v;
if(u==t) return cap;
for(int e=first[u];e!=-1;e=edge[e].next)
{
if(edge[e].c && dist[v=edge[e].v]==dist[u]+1 && ret<cap)
{
long long flow=dinic_dfs(v,min(cap-ret,edge[e].c));
if(flow>0)
{
edge[e].c-=flow;
edge[e^1].c+=flow;
ret+=flow;
}
}
}
if(!ret) dist[u]=-1;
return ret;
}
long long dinic()
{
long long ans=0,tmp;
while(dinic_bfs())
while(tmp=dinic_dfs(s,INF)) ans+=tmp;
return ans;
}
void dfs(int u)
{
vis[u]=1;
for(int e=first[u];e!=-1;e=edge[e].next)
if(edge[e].c>0 && !vis[edge[e].v]) dfs(edge[e].v);
}
int main()
{
// freopen("test.txt","r",stdin);
scanf("%d%d",&N,&M);
s=0;t=N+1;
long long sum=0;
memset(first,-1,sizeof(first));size=0;
for(int i=1;i<=N;i++)
{
long long a;
scanf("%lld",&a);
if(a>0)
{
sum+=a;insert(0,i,a);insert(i,0,0);
}
else
{
insert(i,t,-a);insert(t,i,0);
}
}
for(int i=0;i<M;i++)
{
int u,v;
scanf("%d%d",&u,&v);
insert(u,v,INF);insert(v,u,0);
}
long long din=dinic();
memset(vis,0,sizeof(vis));int cnt=0;
dfs(0);
for(int i=1;i<=N;i++)
if(vis[i]) cnt++;
printf("%d %lld\n",cnt,sum-din);
return 0;
}