牛牛种小树
题目链接:nowcoder 225544
到牛客看:
题目大意
要你构造一个 n 个节点的数,然后给出你 f(i),要你最大化 ∑i=1~n f(d[i])。
其中 d[i] 为 i 点的度数。
思路
我们考虑转换一下题意。
由于一个树的边数是 n − 1 n-1 n−1,那所有点的度数之和就是 2 n − 2 2n-2 2n−2。
那就相当于我们要用 n n n 个数凑出 2 n − 2 2n-2 2n−2,而且这些数都要在 1 ∼ n − 1 1\sim n-1 1∼n−1 之间。
那不难看出这是个完全背包,但是它规定了数选的个数一定要是 n n n 啊。
那我们考虑用这么一个方法,就是在背包的过程中保证它的个数不变。
我们考虑一开始是链的形式,那就是
n
−
2
n-2
n−2 个度数为
2
2
2 的,
2
2
2 个度数为
1
1
1 的。
然后我们背包设
f
i
,
j
f_{i,j}
fi,j 为当前最大的度数是
i
i
i,还有
j
j
j 个度数为
2
2
2 的。
然后你每次要构度数
i
i
i,你就拿
i
−
1
i-1
i−1 个度数为二的点,把
i
−
1
i-1
i−1 个点的其中一个度数全部分配到一个点上,这样就多了
n
−
1
n-1
n−1 个度数为
1
1
1 的点,
1
1
1 个度数为
i
i
i 的点,少了
i
−
1
i-1
i−1 个度数为
2
2
2 的点,计算一下贡献的改变就好了。
然后由于普通的完全背包是
n
3
n^3
n3 不能过,我们这里用到的是单调队列优化背包。
然后复杂度就变成
n
2
n^2
n2,是能过的。
代码
#include<map>
#include<queue>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
int n, bst, tmp;
ll a[10001], f[2][10001], ans;//f 滚动了
int main() {
scanf("%d", &n);
for (int i = 1; i < n; i++) scanf("%lld", &a[i]);
f[2 & 1][n - 2] = 2ll * a[1] + 1ll * (n - 2) * a[2];//初始状态时链
for (int i = 3; i < n; i++) {//新加入的度数
for (int j = n - 2; (n - 2) - j < i - 1; j--) {//多重背包
f[i & 1][j] = f[(i ^ 1) & 1][j];
for (int k = j - (i - 1); k >= 0; k -= (i - 1)) {//枚举现在有多少个度数为 2 的
f[i & 1][k] = max(f[(i ^ 1) & 1][k], f[i & 1][k + (i - 1)] + a[i] - (i - 1) * a[2] + (i - 2) * a[1]);
}
}
}
for (int i = 0; i <= n - 2; i++)//最后剩多少个 2 都可以
ans = max(ans, f[(n ^ 1) & 1][i]);
printf("%lld", ans);
return 0;
}