最大权闭合图-poj2987

来源:互联网 发布:灯塔听力测试软件 编辑:程序博客网 时间:2024/05/22 04:42

最大权闭合图(讲解转自http://www.cnblogs.com/wuyiqi/archive/2012/03/12/2391960.html):

在一个图中,我们选取一些点构成集合,记为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}。

了解了最大权闭合图的概念,接下来我们就需要知道如何求最大权闭合图。上图即被转化为如左图网络。

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

上图即被转化为如左图网络。

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

  • 证明:最小割为简单割。

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

        那么为什么最小割是简单割呢?因为除S和T之外的点间的边的容量是正无穷,最小割的容量不可能为正无穷。所以,得证。

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

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

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

  • 证明最小割所产生的两个集合中,其源点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)。得正。

 

至此,我们就将最大权闭合图问题转化为了求最小割的问题。求最小割用最小割容量=最大流,即可将问题转化为求最大流的问题。

求最少的割边数目,可以从源点对残量网络进行一次DFS,每个割都会将源点和汇点隔开,所以从源点DFS下去一定会碰到某个割dfs停止,这时遍历过的点数就是S集的最少点数。
下面是代码:

#include<iostream>#include<set>#include<map>#include<vector>#include<queue>#include<cmath>#include<climits>#include<cstdio>#include<string>#include<cstring>#include<algorithm>typedef long long LL;using namespace std;const int MAX_N=5010;const int MAX_M=70000;const LL INF=(1<<30);struct node{    int v,next;    LL f;} edge[MAX_M*7];LL ans1,ans2;int N,M;int nn,s,t,num;int head[MAX_N],pre[MAX_N],gap[MAX_N],cur[MAX_N],dis[MAX_N],vis[MAX_N];void add_edge(int u,int v,LL f){    edge[num].v=v;    edge[num].f=f;    edge[num].next=head[u];    head[u]=num++;    edge[num].v=u;    edge[num].f=0;    edge[num].next=head[v];    head[v]=num++;}LL sap(){    for(int i=0; i<=nn; i++)    {        cur[i]=head[i];        gap[i]=dis[i]=0;    }    int u;    LL flow=0,aug=INF;    gap[s]=nn;    u=s;    pre[s]=s;    bool flag;    while(dis[s]<nn)    {        flag=0;        for(int &j=cur[u]; j!=-1; j=edge[j].next)        {            int v=edge[j].v;            if(edge[j].f>0&&dis[u]==dis[v]+1)            {                flag=1;                if(edge[j].f<aug)                    aug=edge[j].f;                pre[v]=u;                u=v;                if(u==t)                {                    flow+=aug;                    while(u!=s)                    {                        u=pre[u];                        edge[cur[u]].f-=aug;                        edge[cur[u]^1].f+=aug;                    }                    aug=INF;                }                break;            }        }        if(flag)            continue;        int mindis=nn;        for(int j=head[u];j!=-1;j=edge[j].next)        {            int v=edge[j].v;            if(edge[j].f>0&&mindis>dis[v])            {                mindis=dis[v];                cur[u]=j;            }        }        if((--gap[dis[u]])==0)            break;        gap[dis[u]=mindis+1]++;        u=pre[u];    }    return flow;}void dfs(int st){    vis[st]=1;    for(int i=head[st];i!=-1;i=edge[i].next)    {        if(!vis[edge[i].v]&&edge[i].f>0)        {            dfs(edge[i].v);            ans2++;        }    }}int main(){    //freopen("in.txt","r",stdin);    LL x,y;    while(cin>>N>>M)    {        s=0,t=N+1,num=0,nn=N+2;        LL sum=0;        memset(head,-1,sizeof(head));        for(int i=1; i<=N; i++)        {            cin>>x;            if(x>0)            {                add_edge(s,i,x);                sum+=x;            }            else add_edge(i,t,-x);        }        for(int i=1;i<=M;i++)        {            cin>>x>>y;            add_edge(x,y,INF);            //add_edge(y,x,INF);        }        ans1=sum-sap();        ans2=0;        memset(vis,0,sizeof(vis));        dfs(s);        cout<<ans2<<' '<<ans1<<endl;    }    return 0;}


原创粉丝点击