最大权闭合图

定义:在一个图中,我们选取一些点构成集合,记为V,且集合中的出边(即集合中的点的向外连出的弧),所指向的终点(弧头)也在V中,则我们称V为闭合图。最大权闭合图即在所有闭合图中,集合中点的权值之和最大的V,我们称V为最大权闭合图。

做法:首先我们将其转化为一个网络(现在不要问为什么,接下来会证明用网络可以求解)。构造一个源点S,汇点T。我们将S与所有权值为正的点连一条容量为其权值的边,将所有权值为负的点与T连一条容量为其权值的绝对值的边,原来的边将其容量定为正无穷。

首先引入结论,最小割所产生的两个集合中,其源点S所在集合(除去S)为最大权闭合图,接下来我们来说明一些结论。

1.最小割为简单割

简单割的概念:割集的每条边都与S或T关联。(请下面阅读时一定分清最小割与简单割,容易混淆)

2.网络中的简单割与原图中闭合图存在一一对应的关系。(即所有闭合图都是简单割,简单割也必定是一个闭合图)

闭合图是简单割:如果闭合图不是简单割(反证法)。那么说明有一条边是容量为正无穷的边,则说明闭合图中有一条出边的终点不在闭合图中,矛盾。

简单割是闭合图:因为简单割不含正无穷的边,所以不含有连向另一个集合(除T)的点,所以其出边的终点都在简单割中,满足闭合图定义。

3.最小割所产生的两个集合中,其源点S所在集合(除去S)为最大权闭合图。

 首先我们记一个简单割的容量为C,且S所在集合为N,T所在集合为M。

        则C=M中所有权值为正的点的权值(即S与M中点相连的边的容量)+N中所有权值为负的点权值的绝对值(即N中点与T中点相连边的容量)。记(C=x1+y1);(很好理解,不理解画一个图或想象一下就明白了)。

        我们记N这个闭合图的权值和为W。

        则W=N中权值为正的点的权值-N中权值为负的点的权值的绝对值。记(W=x2-y2);

        则W+C=x1+y1+x2-y2。

        因为明显y1=y2,所以W+C=x1+x2;

        x1为M中所有权值为正的点的权值,x2为N中权值为正的点的权值。

        所以x1+x2=所有权值为正的点的权值之和(记为TOT).

        所以我们得到W+C=TOT.整理一下W=TOT-C.

        到这里我们就得到了闭合图的权值与简单割的容量的关系。

        因为TOT为定值,所以我们欲使W最大,即C最小,即此时这个简单割为最小割,此时闭合图为其源点S所在集合(除去S)。

 http://cogs.pro/cogs/problem/problem.php?pid=727

/****************************************************************************************************

                                    最大权闭合图—最小割
                                        dinic算法 
                        

********************************************************************************************************/
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define check(c) (c>='0'&&c<='9')
#define inf 9999999
struct Edge{
    int next,to,power;
    Edge(){}
    Edge(int next,int to,int power):next(next),to(to),power(power){}
}edge[10001];
int f[205],top=1,q[205],l,r,dis[202];
inline void add_edge(int c,int t,int p){
    edge[++top]=Edge(f[c],t,p);f[c]=top;
    edge[++top]=Edge(f[t],c,0);f[t]=top;
}
inline bool in(int& a){
    a=0;char c=getchar();
    while(!check(c))c=getchar();
    while(check(c))a=a*10+c-48,c=getchar();
    return (c!='\n'&&c!='\r');
}
bool bfs(){
    memset(dis,-1,sizeof(dis));dis[0]=1;
    l=0,r=1,q[0]=0;
    while(l<r){
        int v=q[l++],to=f[v],x;
        while(to){
            x=edge[to].to;
            if(dis[x]==-1&&edge[to].power>0){
                dis[x]=dis[v]+1,q[r++]=x;
                if(x==201)return 1;
            }
            to=edge[to].next;
        }
    }
    return 0;
}
int dfs(int x,int low){//low为到当前节点最大流
    int ans=0;
    if(x==201)return low;
    int to=f[x],i;
    while(to){
        i=edge[to].to;
        if(dis[i]==dis[x]+1&&(ans=dfs(i,min(low,edge[to].power)))){
            edge[to].power-=ans;//边的最大流量更新 
            edge[to^1].power+=ans;
            return ans;
        }
        to=edge[to].next;
    }
    return ans;
}
int main(){
    freopen("shuttle.in","r",stdin);
    freopen("shuttle.out","w",stdout);
    int n,m,x,sum=0;scanf("%d%d",&m,&n);
    for(int i=1;i<=m;i++){
        bool d=1;
        while(in(x)){
            if(d)add_edge(0,i,x),d=0,sum+=x;
            else add_edge(i,100+x,inf);
        }
        if(d)add_edge(0,i,x);
        else add_edge(i,100+x,inf);
    }
    for(int i=1;i<=n;i++){
        in(x);
        add_edge(100+i,201,x);
    }
    while(bfs()){//判断是否能到n 
            while(int tans=dfs(0,0x7fffffff))sum-=tans;//用增广路更新 
    }
    for(int i=1;i<=m;i++)if(dis[i]!=-1)printf("%d ",i);//最后t集合内的为选用的 
    printf("\n");
    for(int i=1;i<=n;i++)if(dis[i+100]!=-1)printf("%d ",i);
    printf("\n");
    printf("%d",sum);
    return 0;
} 

 

posted @ 2017-04-21 22:02  Bennettz  阅读(234)  评论(0编辑  收藏  举报