有 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=m∗k。问是否存在一种合法的制作菜品方案?要求输出方案。
n ≤ 500 , m , k ≤ 5000 n\le 500,m,k\le 5000 n≤500,m,k≤5000
刚看题的时候被数据范围误到,一直在往网络流和霍尔定理的方向思考。
考虑 m = n − 1 m=n-1 m=n−1 的部分分,发现最小且非零的 d i d_i di 一定满足 d i < k d_i
当 m ≥ n m\ge n m≥n 时,最大的 d i d_i di 一定满足 d i ≥ k d_i\ge k di≥k,用 k k k 克第 i i i 种材料制作一道菜,就会得到满足 m ≥ n − 1 m\ge n-1 m≥n−1 的子问题。
可以发现,对于上述两种情况,在有限步之内一定会结束,表明必定存在解,且可以通过上述方式构造得到。观察数据范围,发现只需考虑 m = n − 2 m=n-2 m=n−2 的情况。
可以通过归纳证明, m = n − 2 m=n-2 m=n−2 的情况有解,当且仅当可以划分为两个满足 m = n − 1 m=n-1 m=n−1 的子问题。因为若把每种菜品看作边,把材料看作点,那么 n − 2 n-2 n−2 条边必然不可能让 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;
}