【NOI2020 制作菜品】

题意

n n n 种材料和 m m m 种菜品,每种菜品需要 k k k 克材料,且同一种菜品只能使用至多两种不同的材料。第 i i i 种材料有 d i d_i di 克,满足 ∑ d i = m ∗ k \sum d_i = m*k di=mk。问是否存在一种合法的制作菜品方案?要求输出方案。
n ≤ 500 , m , k ≤ 5000 n\le 500,m,k\le 5000 n500,m,k5000

分析

刚看题的时候被数据范围误到,一直在往网络流和霍尔定理的方向思考。

考虑 m = n − 1 m=n-1 m=n1 的部分分,发现最小且非零的 d i d_i di 一定满足 d i < k d_idi<k,且一定存在另一个 d j d_j dj 满足 d i + d j ≥ k d_i+d_j\ge k di+djk。如果某道菜分别用 d i , k − d i d_i,k-d_i di,kdi 克第 i , j i,j i,j 种材料,就会得到满足 m = n − 1 m=n-1 m=n1 m = n m=n m=n 的子问题。

m ≥ n m\ge n mn 时,最大的 d i d_i di 一定满足 d i ≥ k d_i\ge k dik,用 k k k 克第 i i i 种材料制作一道菜,就会得到满足 m ≥ n − 1 m\ge n-1 mn1 的子问题。

可以发现,对于上述两种情况,在有限步之内一定会结束,表明必定存在解,且可以通过上述方式构造得到。观察数据范围,发现只需考虑 m = n − 2 m=n-2 m=n2 的情况。

可以通过归纳证明, m = n − 2 m=n-2 m=n2 的情况有解,当且仅当可以划分为两个满足 m = n − 1 m=n-1 m=n1 的子问题。因为若把每种菜品看作边,把材料看作点,那么 n − 2 n-2 n2 条边必然不可能让 n n n 个点都连通,因此必存在至少两个连通块,也就是两个相互独立的子问题。如果上述条件不满足,由归纳假设可以知道肯定无解。

将每个 d i d_i di 减去 k k k 后,等价于问能否选出一个材料的子集,使得其 d i d_i di 的和等于 − k -k k。直接dp是 O ( n 2 k ) O(n^2k) O(n2k) 的,通过bitset优化下就可以过了。

代码

#include
using namespace std;

typedef pair<int, int> pi;

const int N = 505;

int n, m, k, d[N];
set<pi>::iterator it;
bool vis[N];
bitset<N * 10000> f[N];

void solve(set<pi> & se, int m)
{
	if (!m) return;
	int n = se.size();
	if (n <= m)
	{
		it = se.end(); it--;
		pi u = *it; se.erase(it); u.first -= k;
		printf("%d %d\n", u.second, k);;
		if (u.first) se.insert(u);
	}
	else
	{
		it = se.begin(); pi u = *it; se.erase(it);
		it = se.end(); it--; pi v = *it; se.erase(it);
		printf("%d %d %d %d\n", u.second, u.first, v.second, k - u.first);
		v.first -= k - u.first;
		if (v.first) se.insert(v);
	}
	solve(se, m - 1);
}

void solve1()
{
	set<pi> se;
	for (int i = 1; i <= n; i++) se.insert(make_pair(d[i], i));
	solve(se, m);
}

void solve2()
{
	for (int i = 1; i <= n; i++) d[i] -= k;
	f[0].reset(); f[0][n * k] = 1;
	for (int i = 1; i <= n; i++)
		if (!d[i]) f[i] = f[i - 1];
		else if (d[i] > 0) f[i] = f[i - 1] | (f[i - 1] << d[i]);
		else f[i] = f[i - 1] | (f[i - 1] >> (-d[i]));
	if (!f[n][(n - 1) * k]) {puts("-1"); return;}
	memset(vis, 0, sizeof(vis));
	set<pi> se; int now = (n - 1) * k;
	for (int i = n; i >= 1; i--)
		if (!f[i - 1][now]) vis[i] = 1, se.insert(make_pair(d[i] + k, i)), now -= d[i];
	solve(se, (int)se.size() - 1);
	se.clear();
	for (int i = 1; i <= n; i++) if (!vis[i]) se.insert(make_pair(d[i] + k, i));
	solve(se, (int)se.size() - 1);
}

int main()
{
	freopen("dish.in", "r", stdin);
	freopen("dish.out", "w", stdout);
	int T; scanf("%d", &T);
	while (T--)
	{
		scanf("%d%d%d", &n, &m, &k);
		for (int i = 1; i <= n; i++) scanf("%d", &d[i]);
		if (m > n - 2) solve1();
		else solve2();
	}
	return 0;
}

你可能感兴趣的:(乱搞)