POJ 2987 Firing 【最大权闭合图】

来源:互联网 发布:linux监控tomcat进程 编辑:程序博客网 时间:2024/05/21 18:42

 POJ 2987 题目链接

 

题意:

公司要进行裁员,裁掉一个人可以获得一定的利益或损失(用正数和负数表示)。

一些人之间有附属关系,比如b是a的下属,则裁掉a就必须裁掉b。

问要取得最大的利益,最少要裁掉几个人,取得的最大利益是多少


结论一:最大权闭合图的的权=原图中权值为正的点的和 - 最小割(最大流)

结论二:最小割对应取点方案就是最小取点数

具体详细证明过程 请参考:胡伯涛的论文《最小割模型在信息学竞赛中的应用》链接  


大佬的证明:

结论一:
首先引入结论,最小割所产生的两个集合中,其源点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)。得正。

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


结论二:

下面证明最小割对应取点方案就是最小取点数
由于原图是个DAG图,所以对于取得的最大权闭合图K,取它的任意一个子图G,如果从K-G仍然是一个闭合图,那么的点权和一定大于等于0,例如:1->2,2->3,1->4,4->5,若最大权闭合图为:{1,2,3,4,5},那么其中任一满足条件的G({1},{1,2},{1,2,3},{1,4},{1,4,5})点权和一定大于等于0,否则去除G,K-G仍然为闭合图,但是K-G的点权和会大于K
所以如果有两种取点方式使得权值相同,但是取点数不同的话,那么肯定存在一个可以移除的满足条件的子图G,其点权和为0
下面考虑构造的网络,对于G在网络中的对应图G',由于在网络求的是最小割,即最大流,而且G的点权和为0,所以G'中与源点S连边的容量和等于G'中与汇点T连边的流量和,同时由于去除G后K还是一个闭合图,所以只有可能G'中的流量流入K'-G',不可能有流量从K'-G'流入G',所以G'的边中除了流量为inf的那些,一定是满流的
再考虑在残留网络中求出取点集的方法,从源点开始floodfill,忽略满流边,即残留网络中的0流边,可以遍历到的点就是要取的点集了,这个道理想一下简单割和闭合图的取法一一对应就可以了
那么G'既然是满流的,在残留网络中就不可能对这些0流边进行处理,那就不会取到G中的点进入取点集,所以建立网络求得得最小割对应的取法取出的就是最小的点数了


(求点方法二:

再回到本题,第二问求最大利益,显然就是最大权闭合图。

第一问问最少裁掉几个人,我采用的是边权放大的方法:

所有上述3中的边扩大一个很大的倍数k,再减去1,所有上述4中的边扩大同样k倍,再加上1。

设num为一共减去了多少个1,则(maxflow + num) / k仍是最大权闭合图的权,而(maxFlow + num) % k就是第一问的答案。


代码

#include<cstring>#include<iostream>#include<cstdio>#include<algorithm>#include<vector>#include<queue>using namespace std;const long long INF=1e16;const int INFF=0x3f3f3f3f;struct edge{    int to,rev;    long long cap;    edge(int x,long long y,int z)    {        to=x;cap=y;rev=z;    }};vector<edge> g[5111];int level[5555],iter[5555],n,m,s;long long sum;bool vis[5555];void add(int from,int to,int cap){    g[from].push_back(edge(to,cap,g[to].size()));    g[to].push_back(edge(from,0,g[from].size()-1));}void bfs(int s){    memset(level,-1,sizeof(level));    level[s]=0;    queue<int> que;    que.push(s);    while(!que.empty())    {        int v=que.front();que.pop();        for(int i=0;i<g[v].size();i++)        {            edge &e=g[v][i];            if(e.cap>0&&level[e.to]<0)            {                level[e.to]=level[v]+1;                que.push(e.to);            }        }    }}long long dfs(int v,int t,long long  f){    if(v==t)return f;    for(int &i=iter[v];i<g[v].size();i++)    {        edge &e=g[v][i];        if(e.cap>0&&level[e.to]>level[v])        {            long long d=dfs(e.to,t,min(f,e.cap));            if(d>0)            {                e.cap-=d;                g[e.to][e.rev].cap+=d;                return d;            }        }    }    return 0;}long long max_flow(int s,int t){    long long  flow=0;    while(1)    {        bfs(s);        if(level[t]<0)return flow;        memset(iter,0,sizeof(iter));        long long f;        while((f=dfs(s,t,INF))>0)        {            flow+=f;        }    }}int dfs1(int u){    vis[u]=true;    s++;    for(int v=0;v<g[u].size();v++)    {        edge &e=g[u][v];        if(e.cap>0&&!vis[e.to])        {            vis[e.to]=true;            dfs1(e.to);        }    }}int main(){    int x,y;    while(scanf("%d%d",&n,&m)!=EOF)    {        for(int i=0;i<=n+1;i++)            g[i].clear();            sum=0;            long long xx;        for(int i=1;i<=n;i++)        {            scanf("%d",&x);            xx=xx*10000;            if(x>0)             {                 add(0,i,x);             sum+=x;             }            else             add(i,n+1,-x);        }        for(int i=1;i<=m;i++)        {            scanf("%d%d",&x,&y);            add(x,y,INF);        }        long long ans=sum-max_flow(0,n+1);        s=-1;        memset(vis,false,sizeof(vis));        dfs1(0);        printf("%d %I64d\n",s,ans);    }    return 0;}