POJ 2987 Firing (最大权闭合图)

87 篇文章 0 订阅
20 篇文章 0 订阅
Firing
Time Limit: 5000MS Memory Limit: 131072K
Total Submissions: 9823 Accepted: 2955

Description

You’ve finally got mad at “the world’s most stupid” employees of yours and decided to do some firings. You’re now simply too mad to give response to questions like “Don’t you think it is an even more stupid decision to have signed them?”, yet calm enough to consider the potential profit and loss from firing a good portion of them. While getting rid of an employee will save your wage and bonus expenditure on him, termination of a contract before expiration costs you funds for compensation. If you fire an employee, you also fire all his underlings and the underlings of his underlings and those underlings’ underlings’ underlings… An employee may serve in several departments and his (direct or indirect) underlings in one department may be his boss in another department. Is your firing plan ready now?

Input

The input starts with two integers n (0 < n ≤ 5000) and m (0 ≤ m ≤ 60000) on the same line. Next follows n + m lines. The firstn lines of these give the net profit/loss from firing the i-th employee individuallybi (|bi| ≤ 107, 1 ≤ in). The remaining m lines each contain two integers i andj (1 ≤ i, jn) meaning the i-th employee has thej-th employee as his direct underling.

Output

Output two integers separated by a single space: the minimum number of employees to fire to achieve the maximum profit, and the maximum profit.

Sample Input

5 5
8
-9
-20
12
-10
1 2
2 5
1 4
3 4
4 5

Sample Output

2 2

Hint

As of the situation described by the sample input, firing employees 4 and 5 will produce a net profit of 2, which is maximum.

Source


题目大意:

    有一个疯子老板要开除一些员工,开除一个人,那个人的下属,下属的下属,下属的下属的下属……全部都要开除。开除每个人都可以得到一定的收益,有人为正,有人为负。给出每个人都收益以及员工之间的关系。问怎么开除人能使收益最大。


解题思路:

    开始想了很久都没想出来,后来看了别人题解才知道这是一个最大权闭合图。

    从起点s出发建立有向边,终点为每一个权值为正的点,容量为点的权值。以每一个权值为负的点出发,建立有向边,终点为汇点t,权值为节点权值的绝对值。再建立上司到下属权值为无穷的有向边,则最大权闭合图就可以用网络流来求解。有两个结论

1、最大收益=所有权值为正的节点权值之和-最小割。

2、裁员人数=残余网络中与从s出发可以到达的节点的数目。

    由于本人智商不足参考了很多人的文章才学会:

http://www.hankcs.com/program/algorithm/poj-2987-firing.html

http://www.cnblogs.com/wuyiqi/archive/2012/03/12/2391960.html

http://tieba.baidu.com/p/1575136443


附AC代码:

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <vector>
#include <queue>
using namespace std;
#define INF 0x3f3f3f3f
#define LL long long

//用于表示边的结构体(终点、容量、反向边)
struct Edge
{
    int to,cap,rev;
    Edge(int t,int c,int r):to(t),cap(c),rev(r){}
};

const int maxn=5000+5;
int N,M,V;
vector<Edge> G[maxn];//邻接表表示图
int level[maxn];//顶点到源点到距离标号
int iter[maxn];//当前弧,在其之前的边已经没有用了

//向图中添加一条从from到to的容量为cap的边
void add_edge(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)//通过BFS计算从源点出发的距离标号
{
    memset(level,-1,sizeof level);
    queue<int> que;
    level[s]=0;
    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);
            }
        }
    }
}

int dfs(int v,int t,int f)//通过DFS寻找增广路
{
    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[v]<level[e.to])
        {
            int 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;
}

LL max_flow(int s,int t)//用Dinic算法,求从s到t的最大流
{
    LL flow=0;
    for(;;)
    {
        bfs(s);
        if(level[t]<0)
            return flow;
        memset(iter,0,sizeof iter);
        int f;
        while((f=dfs(s,t,INF))>0)
            flow+=f;
    }
}

bool vis[maxn];
int left_points;

void DFS(int s)//遍历残余网络统计s可以到达的点点数量
{
    vis[s]=true;
    ++left_points;
    for(int i=0;i<G[s].size();++i)
        if(!vis[G[s][i].to]&&G[s][i].cap>0)
            DFS(G[s][i].to);
}

int main()
{
    scanf("%d%d",&N,&M);
    V=N+2;
    int s=0,t=N+1;//s为源点,t为汇点
    LL sum=0;//所有权值为正的点之和
    for(int i=1;i<=N;++i)
    {
        int c;
        scanf("%d",&c);
        if(c>0)
        {
            add_edge(s,i,c);
            sum+=c;
        }
        else if(c<0)
            add_edge(i,t,-c);
    }
    for(int i=0;i<M;++i)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        add_edge(a,b,INF);
    }
    LL get=sum-max_flow(s, t);//最大收益
    DFS(s);
    printf("%d %lld\n",left_points-1,get);
    
    return 0;
}


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值