主要记录一下做题过程。
首先题目看上去很不好处理,考虑从部分分的角度入手。
先看 \(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;
}