Loading

Luogu6775 [NOI2020] 制作菜品 做题记录

link

主要记录一下做题过程。

首先题目看上去很不好处理,考虑从部分分的角度入手。

先看 \(m=n-1\) 的部分分,这个性质让我们很容易想到一棵树。考虑把原材料当作点,菜品当作边,一道连接 \((x,y)\) 的菜品表示只能用编号为 \(x\)\(y\) 的原材料。

对于这棵树,我们每次选择一个叶子,然后处理掉连接叶子的边表示的菜品,并删掉叶子。可以发现,一棵树确定了唯一一种方案。

但是这个方案不一定合法。当叶子原材料数量 \(> k\) 或一个点的原材料数量被减到负数时,方案不合法。

我们考虑归纳构造,不断将点数减小。一种很直接的想法是设 \(x,y\)\(d\) 最大和最小的点,每次在 \(x,y\) 之间连一条边,然后保留 \(x\),更新 \(d_x\gets d_x + d_y - k\)

这是否一定合法?

  • 由于 \(\sum\limits_{i = 1} ^ n d_i= mk = (n - 1)k\),得 \(\dfrac {\sum_{i = 1} ^ n d_i}n = \dfrac {n - 1}n k\),可知 \(\overline{d} < k\),那么一定有 \(d_y < k\)

  • 考虑最坏情况下,除了 \(d_y\),其他点的 \(d_i\) 都和 \(d_x\) 相同,此时 \(\sum\limits_{i = 1} ^ n d_i = d_y + (n - 1) d_x = (n - 1)k\),可得 \(\dfrac {d_y}{n - 1} + d_x = k\),那么 \(d_x + d_y > k\)

所以一定合法,这样我们就做完了 \(m = n - 1\) 的部分分。

考虑 \(m \ge n - 1\) 的部分分,很容易想到转化成 \(m = n - 1\) 的形式。考虑归纳构造,一个直接的想法是,每次选取 \(d_1,d_2,...,d_n\) 中最大的点 \(x\),然后直接 \(d_x\gets d_x - k\)。再次验证合法性:

  • 考虑此时 \(m\ge n\),且 \(\sum\limits_{i = 1} ^ n d_i = mk\ge nk\),则 \(\overline d \ge k\),显然有 \(d_x = \max d_i \ge k\)

注意有个坑点,就是如果 \(d_x = k\),那么直接令 \(n\gets n - 1\) 并删掉 \(d_x\),官方数据没有卡,但是洛谷有 Hack 数据。

最后考虑 \(m = n - 2\) 的部分,边数竟然比树还少,此时这张图一定有至少两个连通块。可以发现,三个及以上个连通块一定不优,所以我们只需要枚举一个连通块。

此时这两个连通块都是树,如果找出了连通块,可以转成 \(m = n - 1\) 的形式。考虑一个连通块 \(S\) 的合法条件,为 \(\sum\limits_{i \in S} d_i = (|S| - 1)k\),这个显然可以背包解决。

可以把每个 \(d_i\) 减去 \(k\),就不用存 \(|S|\) 这一维,剩下直接 bitset 即可。注意此时 \(m\)\(n\) 同级,时间复杂度 \(O(\dfrac {n^2k} \omega)\)

点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define ull unsigned ll
#define pir pair <ll, ll>
#define mkp make_pair
#define fi first
#define se second
#define pb push_back
using namespace std;
const ll maxn = 510;
ll t, n, m, k, d[maxn], id1[maxn], id2[maxn], len1, len2;
bitset <5000005> f[maxn];
bool cmp(ll x, ll y) {return d[x] < d[y];}
void sort_insert(ll *id, ll len) {
	ll i, x = id[len];
	for(i = len - 1; i; i--)
		if(d[id[i]] <= d[x]) break;
	for(ll j = len; j > i + 1; j--) id[j] =  id[j - 1];
	id[i + 1] = x;
}
void solve(ll *id, ll len) {
	sort(id + 1, id + 1 + len, cmp);
	while(len > 1) {
		printf("%lld %lld %lld %lld\n", 
			   id[1], d[id[1]], id[len], k - d[id[1]]);
		d[id[len]] += d[id[1]] - k;
		for(ll i = 1; i < len; i++) id[i] = id[i + 1];
		--len, sort_insert(id, len);
	}
}
void scheme(ll i, ll j) {
	if(i == 0) return;
	ll x = d[i] - k;
	if(j - x >= 0 && f[i - 1][j - x]) {
		id1[++len1] = i;
		scheme(i - 1, j - x);
	} else {
		id2[++len2] = i;
		scheme(i - 1, j);
	}
}
int main() {
	scanf("%lld", &t);
	while(t--) {
		scanf("%lld%lld%lld", &n, &m, &k);
		for(ll i = 1; i <= n; i++) scanf("%lld", d + i);
		if(m == n - 2) {
			ll B = m * k;
			f[0].reset(), f[0][B] = 1;
			for(ll i = 1; i <= n; i++) {
				ll x = d[i] - k;
				if(x >= 0) f[i] = f[i - 1] | (f[i - 1] << x);
				else f[i] = f[i - 1] | (f[i - 1] >> (-x));
			}
			if(!f[n][B - k]) {
				puts("-1"); continue;
			} len1 = len2 = 0, scheme(n, B - k);
			solve(id1, len1);
			solve(id2, len2);
		} else {
			for(ll i = 1; i <= n; i++) id1[i] = i;
			sort(id1 + 1, id1 + 1 + n, cmp);
			for(ll i = m; n && i >= n; i--) {
				printf("%lld %lld\n", id1[n], k);
				d[id1[n]] -= k;
				if(d[id1[n]]) sort_insert(id1, n);
				else --n;
			}
			solve(id1, n);
		}
	}
	return 0;
}
posted @ 2024-07-25 09:50  Lgx_Q  阅读(6)  评论(0编辑  收藏  举报