原题: http://poj.org/problem?id=2987
题意:
有一些点和有向边,选择一个点时,会将该点后面(有边连向)的点选中。使选中的点权值和最大,求这个权值以及点的个数。
解析:
和就是说选择一个闭合子图,权值和最大。
定理: 最大闭合图权值
正权点
最小割
(建图:原边建inf,源点到正权点建相应权值,负权点到汇点建负权的绝对值)
使用点数量为残余网络(容量-流量,即算法结束后被减掉一部分发的val数组)的结点数。当然这里因为有一个源点是原来没有的,所以减1。
证明参考:https://blog.csdn.net/winter2121/article/details/80076806
#include<stdio.h>
#include<iostream>
#include<math.h>
#include<algorithm>
#include<string.h>
#include<queue>
using namespace std;
#define LL long long
const int inf=0x3f3f3f3f;
const LL infll=1e18;
const int N=40500,M=405000;
int head[N],nex[M],to[M],val[M],now;
void add(int a,int b,int v){
to[++now]=b;val[now]=v;nex[now]=head[a];head[a]=now;
to[++now]=a;val[now]=0;nex[now]=head[b];head[b]=now;
}
//*********************
int sp,ep,d[N];
int bfs(){
queue<int>Q;
memset(d,-1,sizeof(d));
d[sp]=0;
Q.push(sp);
while(!Q.empty()){
int p=Q.front();Q.pop();
for(int i=head[p];~i;i=nex[i]){
int u=to[i];
if(d[u]==-1&&val[i]>0){
d[u]=d[p]+1;
Q.push(u);
}
}
}
return d[ep]!=-1;
}
int dfs(int p,int v){
int r=0;
if(p==ep)return v;
for(int i=head[p];(~i)&&r<v;i=nex[i]){
int u=to[i];
if(val[i]>0&&d[u]==d[p]+1){
int x=dfs(u,min(val[i],v-r));
r+=x;
val[i]-=x;
val[i^1]+=x;
}
}
if(!r)d[p]=-2;
return r;
}
LL dinic(){
LL ans=0,t;
while(bfs()){
while(t=(LL)dfs(sp,inf))ans+=t;
}
return ans;
}
//***********************
bool viss[N];
int num;
int count(int p){
int ans=1;
viss[p]=1;
for(int i=head[p];~i;i=nex[i]){
int u=to[i];
if(viss[u]||val[i]<=0)continue;
ans+=count(u);
}
return ans;
}
//***********************
void init(){
now=-1;//要求第一条边为0
memset(head,-1,sizeof(head));
}
int n,m;
int V[5002];
int main(){cin>>n>>m;
init();sp=0;ep=5001;
for(int i=1;i<=n;i++)scanf("%d",V+i);
for(int i=1,a,b;i<=m;i++){
scanf("%d%d",&a,&b);
add(a,b,inf);
}
LL sum=0;
for(int i=1;i<=n;i++){
if(V[i]>0)add(0,i,V[i]),sum+=(LL)V[i];
if(V[i]<0)add(i,5001,-V[i]);
}
LL ans=sum-dinic();
memset(viss,0,sizeof(viss));
int num=count(0)-1;
printf("%d %lld\n",num,ans);
}