loj6395. 「THUPC2018」城市地铁规划 / City
题意
给定一个函数\(c(x)\)表示度数为\(x\)的节点权值。求构建一棵树,使得权值和最大。
\(n \leq 3000\)。
题解
考虑要构建一棵树,prufer序列会是一个不错的选择。
根据prufer的性质,每个点出现的次数为度数减1,并且总序列长度为\(n - 2\)。
这样就可以做一个完全背包,体积为\(x - 1\),价值为\(c(x)\),(其中\((1 \leq x \leq n - 1)\))的物品每种有无限个。
最后求出背包大小为\(n - 2\)的最大价值和。
但是注意到体积为0也有价值,却不能放进背包更新。
有一个方法:开始时把\(n\)个体积为\(0\)的物品放进背包,然后剩下的过程就是每次将若干个体积为\(0\)的物品替换为体积为\(x\)的物品。
即\(f_0 = n * c_1\),更新的时候比较\(f_j\)和\(f_{j - (i - 1)} + c_i - c_1\),最后答案为\(f_{n - 2}\)。
但是本题还要输出方案。这就要求dp的时候记录转移的前驱。利用这个我们可以计算出最终的树上的\(n\)个点在prufer序列上出现次数。
然后利用这个贪心的思想构建这棵树(度数越小的放末尾,事实证明,把所有叶子结点放在末尾即可),注意根的特殊性(对于非根节点,在prufer序列出现次数 = 儿子数;对于根,在prufer序列出现次数 = 儿子数 - 1)。
复杂度\(\mathcal O(n ^ 2)\)。
#include <bits/stdc++.h>
using namespace std;
const int N = 3005, mod = 59393;
int n, k, tot, a[N], c[N], f[N], g[N], d[N];
void dfs (int x) {
for (int i = 1; i <= d[x]; ++i) {
printf("%d %d\n", x, ++tot);
dfs(tot);
}
}
int main () {
scanf("%d%d", &n, &k);
for (int i = 0; i <= k; ++i) {
scanf("%d", &a[i]);
}
for (int i = 0; i <= n; ++i) {
for (int j = k; ~j; --j) {
c[i] = c[i] * i % mod + a[j];
c[i] %= mod;
}
}
if (n == 1) {
printf("0 %d\n", c[0]);
return 0;
}
if (n == 2) {
printf("1 %d\n",2 * c[1]);
printf("1 2\n");
return 0;
}
f[0] = n * c[1];
for (int i = 2; i <= n; ++i) {
for (int j = i - 1; j <= n - 2; ++j) {
if (f[j] < f[j - (i - 1)] + c[i] - c[1]) {
f[j] = f[j - (i - 1)] + c[i] - c[1];
g[j] = j - (i - 1);
}
}
}
printf("%d %d\n", n - 1, f[n - 2]);
for (k = n - 2; k; k = g[k]) {
d[++d[0]] = k - g[k];
}
sort(d + 1, d + d[0] + 1);
++d[1];
dfs(tot = 1);
return 0;
}