洛谷 P1791 [国家集训队]人员雇佣 紫 题解

前言

大家都能看出来是一个最小割,在此主要阐述一下连边的正确性。

博客欢迎你,为你开天辟地

连边方式

  1. 源点 \(S\) 向每个点(经理)\(i\),连容量为 \(\sum\limits_{k=1}^n E_{i,k}\) 的边。
  2. 每个点(经理)\(i\) 向汇点 \(T\),连容量为 \(A_i\) 的边。
  3. 每个点(经理)\(i\) 向其他点(经理)\(j\),连容量为 \(2 E_{i, j}\) 的边。
  4. 答案为 \(\sum\limits_{i=1}^n \sum\limits_{j=1}^n E(i,j) - \operatorname{dinic}\)

理论基础

答案最大是多少?自然是所有 \(E\) 相加。

我们令连向 \(S\) 的点为雇佣者;连向 \(T\) 的点为不雇佣者。那么当答案最大时,图就长这样(当然,它不可能达到这个状态)。

v27vtS.png

这个图说明每个经理即雇佣了又没有雇佣,呈一种薛定谔的状态

也就是说每个点我们只可以连 \(S\)\(T\) 的其中一条边!

不考虑冲突,假如我们割去某个点与 \(T\) 的连边,说明我们要雇佣该经理,雇佣该经理就需要一些费用,而这些费用就该体现在这条边上,由此得知每个点连向 \(T\) 的边的容量为 \(A_i\)

不考虑冲突,假如我们割去 \(S\) 与某个点的连边,说明我们要解雇该经理,解雇该经理就造成一定损失,而这些损失就该体现在这条边上,损失具体是多少?自然是所有第 \(i\) 行的 \(E\),因为没了 \(i\) 这个经理,所有与 \(i\) 相关的价值都要被删去,由此得知 \(S\) 连向每个点的边的容量为 \(\sum\limits_{k=1}^n E_{i,k}\)

考虑冲突,为什么要那样连边?先上图!

这个图的情况是选 \(i\) 但是不选 \(j\)

首先如果两个点都连接于 \(S\) 或者 \(T\) ,那么中间的边是不起作用的,因为你们都在一个团队里了还互相残杀就说不过去了。

\(i\)\(S\) 中,\(j\)\(T\) 中,对于 \(i\) 他是对所在团队有 \(\sum\limits_{k=1}^n E_{i,k}\) 的贡献,但是!但是! \(j\)\(i\) 认识,因为 \(j\) 知道 \(i\) 的底细,所以 \(i\) 对团队做出的贡献中 \(E_{i,j}\) 白搭了!不仅白搭!还会减少 \(E_{i,j}\)惊不惊喜?意不意外?,所以团队总贡献一下子少了 \(2 E_{i,j}\),那么连的边的权值自然为 \(2E_{i,j}\)

这告诉我们一个真理:不要把自己完全裸露在对方的视野里,要留一手备长局,步入社会后就是这样。

所以说跑一边最大流求最小割,用总价值减去它即可。

Code

#include <iostream>
#include <cstring>

using namespace std;

const int N = 1010 * 1010, M = 2 * (N * 3);
const int INF = 0x3f3f3f3f;

int n, S, T, sum;
int maze[1010][1010];
int h[N], e[M], f[M], ne[M], idx;
int q[N], cur[N], depth[N];

int read()
{
	int s = 0, w = 1;
	char c = getchar();
	while (c < '0' || c > '9')
	{
		if (c == '-') w = -1;
		c = getchar();
	}
	while (c >= '0' && c <= '9')
	{
		s = s * 10 + c - '0';
		c = getchar();
	}
	
	return s * w;
}

void add(int a, int b, int c)
{
	e[idx] = b;
	f[idx] = c;
	ne[idx] = h[a];
	h[a] = idx ++ ;
	
	e[idx] = a;
	f[idx] = 0;
	ne[idx] = h[b];
	h[b] = idx ++ ;
	
	return;
}

bool bfs()
{
	int hh = 0, tt = 0;
	memset(depth, -1, sizeof depth);
	q[0] = S;
	depth[S] = 0;
	cur[S] = h[S];
	
	while (hh <= tt)
	{
		int t = q[hh ++ ];
		for (int i = h[t]; ~i; i = ne[i])
		{
			int ver = e[i];
			if (depth[ver] == -1 && f[i] > 0)
			{
				depth[ver] = depth[t] + 1;
				cur[ver] = h[ver];
				q[ ++ tt] = ver;
				if (ver == T) return true;
			}
		}
	}
	
	return false;
}

int dfs(int u, int lmt)
{
	if (u == T) return lmt;
	int flow = 0;
	for (int i = cur[u]; ~i && flow < lmt; i = ne[i])
	{
		cur[u] = i;
		int ver = e[i];
		if (depth[ver] == depth[u] + 1 && f[i] > 0)
		{
			int t = dfs(ver, min(f[i], lmt - flow));
			if (!t) depth[ver] = -1;
			f[i] -= t;
			f[i ^ 1] += t;
			flow += t;
		}
	}
	
	return flow;
}

int dinic()
{
	int res = 0, flow = 0;
	while (bfs())
		while (flow = dfs(S, INF))
			res += flow;
	return res;
}

int main()
{
	n = read();
	S = 0, T = n + 1;
	memset(h, -1, sizeof h);
	for (int i = 1; i <= n; i ++ )
	{
		int a = read();
		add(i, T, a);
	}
	for (int i = 1; i <= n; i ++ )
	{
		int tot = 0;
		for (int j = 1; j <= n; j ++ )
		{
			cin >> maze[i][j];
			sum += maze[i][j];
			tot += maze[i][j];
		}
		add(S, i, tot);
	}
	
	for (int i = 1; i <= n; i ++ )
		for (int j = 1; j <= n; j ++ )
		{
			if (i == j) continue;
			add(i, j, 2 * maze[i][j]);
		}
	
	cout << sum - dinic() << endl;
	
	return 0;
}

后语

思考是好的,完结撒花!

posted @ 2022-08-26 16:10  LittleMoMol  阅读(68)  评论(0编辑  收藏  举报
*/