【POJ 2987】Firing

【题目】

传送门

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 ≤ ij ≤ 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

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.

【分析】

大致题意:一个公司有 n 个员工 m 对从属关系,每辞退一个员工可以得到一个价值 b_ib_i 可以为负),但每辞退一个员工都要把他的下属一起辞退,求能获得的最大价值时辞退的最少员工数以及能获得的最大价值

在说这道题之前,先说一下最大权闭合子图吧

不太规范的定义:一个子图(点集),如果所有点的所有出边都在这个子图当中,那么它就是闭合子图。点权和最大的闭合子图就是最大闭合子图。

扫描二维码关注公众号,回复: 3457150 查看本文章

求法:新增两个虚点,一个是源点 s,一个是汇点 t;设这个图中一个点的点权为 x,若 x ≥ 0,则从 s 向这个点连一条边权为 x 的边,不然就从这个点向 t 连一条边权为 -x 的边;图中原有的边的边权就是 inf;跑完最大流后,原来正的点权和减去最大流就是答案

证明的话我不会……

然后看这道题,每个上司都向下属连边,然后删掉点 x 后要把从 x 连边的所有点删掉,根据定义,这就是一道典型的最大权闭合子图,连边跑一遍最大流即可

对于第一问,被辞退的员工应该是会被全部流满的,因此再 dfs 一遍即可

【代码】

#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 100005
#define M 10000005
#define inf (1ll<<31ll)-1
using namespace std;
int n,m,s,t,num=1;
int v[M],w[M],next[M];
int d[N],f[N],first[N];
bool vis[N];
void add(int x,int y,int k)
{
	num++;
	next[num]=first[x];
	first[x]=num;
	v[num]=y;
	w[num]=k;
}
bool bfs()
{
	int x,y,i,j;
	memset(d,-1,sizeof(d));
	memcpy(f,first,sizeof(f));
	queue<int>q;d[s]=0;q.push(s);
	while(!q.empty())
	{
		x=q.front();
		q.pop();
		for(i=first[x];i;i=next[i])
		{
			y=v[i];
			if(w[i]&&d[y]==-1)
			{
				d[y]=d[x]+1;
				if(y==t)
				  return true;
				q.push(y);
			}
		}
	}
	return false;
}
int dinic(int now,int flow)
{
	if(now==t)  return flow;
	int x,delta,ans=0;
	for(int &i=f[now];i;i=next[i])
	{
		x=v[i];
		if(w[i]&&d[x]==d[now]+1)
		{
			delta=dinic(x,min(flow,w[i]));
			w[i]-=delta,w[i^1]+=delta;
			ans+=delta,flow-=delta;
			if(!flow)  return ans;
		}
	}
	return ans;
}
int dfs(int x)
{
	int i,j,ans=1;
	vis[x]=true;
	for(i=first[x];i;i=next[i])
	{
		j=v[i];
		if(w[i]&&!vis[j])
		  ans+=dfs(j);
	}
	return ans;
}
int main()
{
	int x,y,i,k;
	long long sum=0;
	scanf("%d%d",&n,&m);
	s=0,t=n+1;
	for(i=1;i<=n;++i)
	{
		scanf("%d",&k);
		if(k<0)  add(i,t,-k),add(t,i,0);
		else  add(s,i,k),add(i,s,0),sum+=k;
	}
	for(i=1;i<=m;++i)
	{
		scanf("%d%d",&x,&y);
		add(x,y,inf),add(y,x,0);
	}
	while(bfs())
	  sum-=dinic(s,inf);
	int ans=dfs(s)-1;
	printf("%d %lld",ans,sum);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/forever_dreams/article/details/82952048
POJ