Firing(最大闭合图 最大流)

原题: http://poj.org/problem?id=2987

题意:

有一些点和有向边,选择一个点时,会将该点后面(有边连向)的点选中。使选中的点权值和最大,求这个权值以及点的个数。

解析:

和就是说选择一个闭合子图,权值和最大。

定理: 最大闭合图权值 = =\sum 正权点 - 最小割
(建图:原边建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);
}

猜你喜欢

转载自blog.csdn.net/jk_chen_acmer/article/details/86599322