POJ 2987 - Firing (最大权闭合子图)

题意:求一个图的最大权闭合子图

教科书:https://blog.csdn.net/can919/article/details/77603353

最小割模型的应用,源点向正权点建容量为该权值的弧;负权点向汇点建容量为该权值绝对值的弧;有偏序关系的点对之间建容量为正无穷的弧.
跑出最大流,根据最大流最小割定理:最大流的值即最小割.所求最大闭合子图的值为|正权点权值之和| - |最小割|.
此外,还要求删去人的数目.那么从源点出发,且不是最小割的边,就是被选择的边.
所以从源点dfs,若遇到容量不为0的边则结果++.最后记得减去源点

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int MAXN=6010;//点数的最大值
const int MAXM=100010;//边数的最大值
#define captype long long
typedef long long LL;
const LL INF = (1LL)<<60;

struct SAP_MaxFlow{
    struct EDGE{
        int to,next;
        captype cap;
    }edg[MAXM];
    int eid,head[MAXN];
    int gap[MAXN];
    int dis[MAXN];
    int cur[MAXN];
    int pre[MAXN];

    void init(){
        eid=0;
        memset(head,-1,sizeof(head));
    }
    void AddEdge(int u,int v,captype c,captype rc=0){
        edg[eid].to=v; edg[eid].next=head[u];
        edg[eid].cap=c;  head[u]=eid++;
        edg[eid].to=u; edg[eid].next=head[v];
        edg[eid].cap=rc; head[v]=eid++;
    }
    captype maxFlow_sap(int sNode,int eNode, int n){//n是包括源点和汇点的总点个数,这个一定要注意
        memset(gap,0,sizeof(gap));
        memset(dis,0,sizeof(dis));
        memcpy(cur,head,sizeof(head));
        pre[sNode] = -1;
        gap[0]=n;
        captype ans=0;
        int u=sNode;
        while(dis[sNode]<n){
            if(u==eNode){
                captype Min=INF ;
                int inser;
                for(int i=pre[u]; i!=-1; i=pre[edg[i^1].to])
                if(Min>edg[i].cap){
                    Min=edg[i].cap;
                    inser=i;
                }
                for(int i=pre[u]; i!=-1; i=pre[edg[i^1].to]){
                    edg[i].cap-=Min;
                    edg[i^1].cap+=Min;
                }
                ans+=Min;
                u=edg[inser^1].to;
                continue;
            }
            bool flag = false;
            int v;
            for(int i=cur[u]; i!=-1; i=edg[i].next){
                v=edg[i].to;
                if(edg[i].cap>0 && dis[u]==dis[v]+1){
                    flag=true;
                    cur[u]=pre[v]=i;
                    break;
                }
            }
            if(flag){
                u=v;
                continue;
            }
            int Mind= n;
            for(int i=head[u]; i!=-1; i=edg[i].next)
            if(edg[i].cap>0 && Mind>dis[edg[i].to]){
                Mind=dis[edg[i].to];
                cur[u]=i;
            }
            gap[dis[u]]--;
            if(gap[dis[u]]==0) return ans;
            dis[u]=Mind+1;
            gap[dis[u]]++;
            if(u!=sNode) u=edg[pre[u]^1].to;  //退一条边
        }
        return ans;
    }
}F;


bool vis[MAXN];
int tot = 0;
void dfs(int u)
{
    vis[u] = true;
    tot++;
    for(int i=F.head[u];~i;i=F.edg[i].next){
        int v = F.edg[i].to;
        if(!vis[v] && F.edg[i].cap>0){
            dfs(v);
        }
    }
}

LL val[MAXN];

int main()
{
    #ifndef ONLINE_JUDGE
        freopen("in.txt","r",stdin);
        freopen("out.txt","w",stdout);
    #endif
    int n,m;
    while(scanf("%d %d",&n,&m)==2){
        F.init();
        LL sum = 0;
        tot=0;
        int s = 0,t = n+1,u,v;
        for(int i=1;i<=n;++i){
            scanf("%lld",&val[i]);
            if(val[i]>0){
                sum += val[i];                  //统计正权边之和
                F.AddEdge(s,i,val[i]);
            }
            else{
                F.AddEdge(i,t,-val[i]);
            }
        }
        for(int i=1;i<=m;++i){
            scanf("%d %d",&u,&v);
            F.AddEdge(u,v,INF);         
        }
        LL f = F.maxFlow_sap(s,t,t+1);
        LL res = sum - f;                       //最大权值为 正权和-最小割
        memset(vis,0,sizeof(vis));
        dfs(s);                                 //计算割的边数
        printf("%d %lld\n",tot-1,res);          //多算了一个
    }
    return 0;
}

posted @ 2018-09-14 20:49  xiuwenL  阅读(129)  评论(0编辑  收藏  举报