Firing(POJ-2987)

Problem 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 first n lines of these give the net profit/loss from firing the i-th employee individually bi (|bi| ≤ 107, 1 ≤ i ≤ n). The remaining m lines each contain two integers i and j (1 ≤ i, j ≤ n) meaning the i-th employee has the j-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

题意:有 n 个人,每个人有一个价值,现在这 n 个人中有 m 个关系 x y,表示 y 是 x 的下级,现在要进行裁员,裁掉一个员工后,也会将他的下级裁掉,现在要求裁掉若干人,使得获得的价值最大,问裁掉的人数与价值

思路:最大权闭合子图裸题

首先记录整个图中所有正点权之和,然后建图

设一个超级源点 S 与一个超级汇点 T,从源点 S 向每个正权点连一条容量为权值的边,每个负权点向汇点 T 连一条容量为权值的绝对值的边,原图中的边容量均设为 INF

在建完图后,利用 Dinic 求最小割,最终人数是最小割中的元素个数,价值是正权值之和-最小割的值

注意使用 long long

Source Program

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
#include<cmath>
#include<ctime>
#include<algorithm>
#include<utility>
#include<stack>
#include<queue>
#include<vector>
#include<set>
#include<map>
#include<bitset>
#define PI acos(-1.0)
#define INF 0x3f3f3f3f
#define LL long long
#define Pair pair<LL,LL>
LL quickPow(LL a,LL b){ LL res=1; while(b){if(b&1)res*=a; a*=a; b>>=1;} return res; }
LL multMod(LL a,LL b,LL mod){ a%=mod; b%=mod; LL res=0; while(b){if(b&1)res=(res+a)%mod; a=(a<<=1)%mod; b>>=1; } return res%mod;}
LL quickPowMod(LL a, LL b,LL mod){ LL res=1,k=a; while(b){if((b&1))res=multMod(res,k,mod)%mod; k=multMod(k,k,mod)%mod; b>>=1;} return res%mod;}
LL getInv(LL a,LL mod){ return quickPowMod(a,mod-2,mod); }
LL GCD(LL x,LL y){ return !y?x:GCD(y,x%y); }
LL LCM(LL x,LL y){ return x/GCD(x,y)*y; }
const double EPS = 1E-10;
const int MOD = 998244353;
const int N = 200000+5;
const int dx[] = {-1,1,0,0,1,-1,1,1};
const int dy[] = {0,0,-1,1,-1,1,-1,1};
using namespace std;

struct Edge{
    LL from,to;
    LL cap,flow;
    Edge(){}
    Edge(LL from,LL to,LL cap,LL flow):from(from),to(to),cap(cap),flow(flow){}
};
LL n,m;             //结点数,边数(含反向弧)
LL S,T;             //源点、汇点
vector<Edge> edges;  //边表,edges[e]和edges[e^1]互为反向弧
vector<LL> G[N];    //邻接表,G[i][j]表示结点i的第j条边在e数组中的序号
bool vis[N];         //BFS使用,标记一个节点是否被遍历过
LL dis[N];          //dis[i]表从起点s到i点的距离(层次)
LL cur[N];          //cur[i]表当前正访问i节点的第cur[i]条弧
set<LL> cutSet;     //最小割
bool flag;           //是否求最小割
void addEdge(LL from,LL to,LL cap){
    edges.push_back( Edge(from,to,cap,0) );
    edges.push_back( Edge(to,from,0,0) );
    LL m=edges.size();
    G[from].push_back(m-2);
    G[to].push_back(m-1);
}
bool BFS(){//构建层次网络
    memset(vis,0,sizeof(vis));
    dis[S]=0;
    vis[S]=true;

    //将超级源点加入最小割
    // if(flag)
    //     cutSet.insert(S);
 
    queue<LL> Q;//用来保存节点编号
    Q.push(S);
    while(!Q.empty()){
        LL x=Q.front();
        Q.pop();
        for(LL y=0;y<G[x].size();y++){
            Edge& e=edges[G[x][y]];
            if(!vis[e.to] && e.cap>e.flow){
                vis[e.to]=true;
                dis[e.to]=dis[x]+1;
                Q.push(e.to);

                if(flag)//记录最小割元素
                    cutSet.insert(e.to);
            }
        }
    }
    return vis[T];
}
 
LL DFS(LL x,LL cp){//cp表示从s到x目前为止所有弧的最小残量
    if(x==T || cp==0)
        return cp;
 
    LL flow=0,newFlow;//flow用来记录从x到t的最小残量
    for(LL &y=cur[x];y<G[x].size();y++){
        Edge &e=edges[G[x][y]];
        if(dis[x]+1==dis[e.to]){
            LL minn=min(cp,e.cap-e.flow);
            newFlow=DFS(e.to,minn);
            if(newFlow>0){
                e.flow+=newFlow;
                edges[G[x][y]^1].flow-=newFlow;
                flow+=newFlow;
                cp-=newFlow;
 
                if(cp==0)
                    break;
            }
        }
    }
    return flow;
}
LL Dinic(){
    LL flow=0;
    while(BFS()){
        memset(cur,0,sizeof(cur));
        flow+=DFS(S,INF);
    }
    return flow;
}
int main(){
    scanf("%lld%lld",&n,&m);

    S=0,T=n+1;//超级源、汇
    LL sum=0;//正权值之和
    for(LL i=1;i<=n;i++){
        LL x;
        scanf("%lld",&x);
        if(x>0){
            addEdge(S,i,x);
            sum+=x;
        }
        else
            addEdge(i,T,-x);
    }
    for(LL i=1;i<=m;i++){
        LL x,y;
        scanf("%lld%lld",&x,&y);
        addEdge(x,y,INF);
    }

    flag=false;//不统计最小割所属元素
    LL minCut=Dinic();//计算最小割的值
    flag=true;//统计最小割所属元素
    BFS();//构建层次网络
    printf("%d ",cutSet.size());//最小割个数
    printf("%lld\n",sum-minCut);//最大权闭合子图的权值

    return 0;
}

 

posted @ 2022-09-20 22:50  老程序员111  阅读(44)  评论(0编辑  收藏  举报