POJ 2987 Firing 【最大权闭合图】

 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;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值