CF1519F Chests and Keys(最小割最大流+状压dp)

题目:洛谷CF1519FCF1519F

题目描述:

\(n\)个盒子,第\(i\)个盒子里面有钱\(a_i\),有\(m\)种锁与其对应的钥匙,每个锁都有无穷多个,购买第\(i\)种钥匙需要花费\(b_i\)钱,在第\(i\)个盒子上放第\(j\)个锁需要花费\(c_{i,j}\)代价

\(Alice\)\(Bob\)在玩游戏,\(Alice\)在花费某些代价在某些盒子上放了某些锁,\(Bob\)需要购买某些钥匙去打开他能打开的箱子,并获得箱子里的钱

打开一个盒子需要打开那个盒子上的所有锁,每种钥匙只能打开它对应的锁,且可以使用多次

\(Bob\)最终赚了钱,那么\(Bob\)赢,否则\(Alice\)

问要让\(Alice\)赢,\(Alice\)最少需要花费多少钱去放置锁,或者无解输出\(-1\)

蒟蒻题解:

假设我们已知\(Alice\)是怎么放锁的,让我们看看\(Bob\)获得最大的收益是多少

\(Bob\)的最大收益可以转化为:所有盒子的钱\(-\)没打开的盒子的钱\(-\)购买锁所花费的钱

要求没打开的盒子的钱\(+\)购买锁所花费的钱的最小值

让我们建网络流的图,源点\(S\)指向每个盒子,流量为对应盒子里面的钱,每个锁指向汇点\(T\),流量为购买对应钥匙需要花费的代价,如果第\(i\)个盒子放了第\(j\)个锁,那么就让第\(i\)个盒子向第\(j\)个锁连边,流量为正无穷大

这样把\(\sum_{i=1}^{n}a_i-\)最小割最大流便是\(Bob\)能获得的最大收益

\(Bob\)输,也就是\(\sum_{i=1}^{n}a_i-\)最小割最大流\(\leq 0\),又网络流图的最大流\(\leq \sum_{i=1}^{n}a_i\),所以把\(\sum_{i=1}^{n}a_i-\)最小割最大流\(\geq 0\),如果不能赚钱,他可以什么都不买,这样收益变为\(0\),所以\(Bob\)输等价于\(\sum_{i=1}^{n}a_i=\)最小割最大流,即这张网络流的图从源点连出去的边满流

如果连向汇点的边满流都无法使从源点连出去的边满流,那么一定是无解

如果对于每种情况跑一遍网络流,那肯定是不行的

由于要求源点连出去的边要满流,观察到\(n,m,a_i,b_i\)都很小,我们可以状压\(dp\),枚举\(b\)剩下的所有状态,一共\(5^m\)种,然后对于每个源点连出去的边使得它满流,由于远远跑不满,预处理一下后,跑到的总的状态数远小于\(5^{2m}\),计算需要\(m\)的时间

时间复杂度\(\Theta(m 5^{2m})\)

参考程序:

#include<bits/stdc++.h>
using namespace std;
#define Re register int

const int Inf = 1e9;
int n, m, sum, N = 1, ans = Inf, a[8], b[8], c[8], g[8], f[8][15630];
vector<int> q[8];

inline int read()
{
	char c = getchar();
	int ans = 0;
	while (c < 48 || c > 57) c = getchar();
	while (c >= 48 && c <= 57) ans = (ans << 3) + (ans << 1) + (c ^ 48), c = getchar();
	return ans;
}

inline int min(int x, int y)
{
	return x < y ? x : y;
}

int main()
{
	n = read(), m = read();
	for (Re i = 1; i <= n; ++i) a[i] = read(), sum += a[i];
	for (Re i = 0; i < m; ++i, N *= 5) b[i] = read(), sum -= b[i];
	if (sum > 0)
	{
		puts("-1");
		return 0;
	}
	f[0][0] = 0, ans, q[0].push_back(0);
	for (Re i = a[1]; i < N; ++i)
	{
		int u = i, v = 0, w = 0, p = 1;
		for (Re j = 0; j < m && u; ++j, u /= 5)
		{
			if (u % 5 > b[j])
			{
				p = 0;
				break;
			}
			v += u % 5;
		}
		if (!p) continue;
		for (Re j = 1; j <= n && v > 0; ++j)
		{
			v -= a[j];
			if (!v)
			{
				w = j;
				break;
			}
		}
		if (w) q[w].push_back(i);
	}
	for (Re i = 1; i <= n; ++i)
	{
		for (Re j = 0; j < m; ++j) c[j] = read();
		int u = q[i].size(), v = q[i - 1].size();
		for (Re j = 0; j < u; ++j)
		{
			int s = q[i][j];
			f[i][j] = Inf;
			for (Re k = 0; k < m; ++k, s /= 5) g[k] = s % 5;
			for (Re k = 0; k < v; ++k)
			{
				int s = q[i - 1][k], p = 1, ss = 0;
				for (Re l = 0; l < m; ++l, s /= 5)
				{
					if (s % 5 > g[l])
					{
						p = 0;
						break;
					}
					if (g[l] > s % 5) ss += c[l];
				}
				if (!p) continue;
				f[i][j] = min(f[i][j], f[i - 1][k] + ss);
			}
		}
	}
	int u = q[n].size();
	for (Re i = 0; i < u; ++i) ans = min(ans, f[n][i]);
	printf("%d", ans);
	return 0;
}
posted @ 2021-05-09 12:31  clfzs  阅读(76)  评论(0)    收藏  举报