题意
①题意中的度指的是对于数中每个结点的边数和,实际上就是图中无向图结点的度
②根据树的特点,每个结点度至少为1,且n个结点的有n-1条边,每条边连接两个点,对这两个点都有一个度的贡献,所以树的度的和为 2*(n-1)
③问题转化为 对 n 个结点选择 1 ~ (n - 1) 的度数,在度数和为 2 * ( n - 1 )的前提下,求最大价值
其实我很好奇这样构造在结点个数和度数满足树的条件时,到底真的实际上能不能构造出这棵树,不知道有无证明?
1.很明显但会T的O(n^3)的做法
n^3的背包dp做法很明显,dp[i][j] 前 i 个 结点 一共花了 j 的度数,可以开一个滚动数组,内存够了,就是时间复杂度不行。
代码如下
#include<bits/stdc++.h> using namespace std; const int N = 2e4 + 10; typedef long long ll; ll dp[2][N]; ll v[N]; int main(){ int n; cin >> n; for(int i = 1; i < n; ++i) cin >> v[i]; memset(dp, -0x3f, sizeof dp); dp[0][0] = 0; for(int i = 1; i <= n; ++i){ for(int j = 1; j <= n-1; ++j) { for(int k = j; k <= 2*n - 2; ++k) dp[i&1][k] = max(dp[i&1][k],dp[(i - 1)&1][k - j] + v[j]); } } cout << dp[n&1][2*n - 2]; return 0; }
2.优化为O(n^2)
现在考虑如何优化,其实上面这个问题是一个完全背包,有 n-1 个物品,度数1 ~ n - 1,价值f(i),每个物品都可以选择多次,但是总共选的物品个数要为n(结点个数) ,物品体积之和(度数和)为2(n - 1),求最大价值。
简单的的完全背包只要枚举物品和体积就行,时间复杂度是n^2是可以过的,所以可以考虑把上面的代码压缩掉一个维度,从完全背包对应过来,对度数(物品)的枚举和总的度数花费(体积)是一定要保留的,所以这里我们可以考虑把总共选的物品(结点个数)个数要为n压缩掉
3.如何压缩
之所以有结点个数这个维度,因为要保证这是一棵有n个结点且每个节点都要有度的树。那么如何提前处理保证这个呢??? 可以让n个结点先各自占用一个度,还剩下 n - 2个度分配给其中的任意结点并对这个结点的度数做更新,这样就能确保每个结点都是合法的了。
整理下,你现在只要做有 n - 2 种的物品,背包体积为n - 2的完全背包就可以了
状态转移方程:dp[j] = max(dp[j],dp[j-i] + v[i + 1] - v[1]); 原先已经给结点分配了一个度的价值,所以要 + v[i + 1] - v[1]
注意初始化,dp[0] = n * v[i] 还没有分配任何度的时候,每个结点度都是v[1],所以总价值为n*v[1]
代码
#include<bits/stdc++.h> using namespace std; const int N = 2e4 + 10; typedef long long ll; ll dp[N]; ll v[N]; int main(){ int n; cin >> n; for(int i = 1; i < n; ++i) cin >> v[i]; dp[0] = v[1] * n;//初始化 for(int i = 1; i <= n - 2; ++i){// 枚举物品(分配给结点的度) for(int j = i; j <= n - 2; ++j)//枚举总共花费的度 { dp[j] = max(dp[j],dp[j-i] + v[i + 1] - v[1]); } } cout << dp[n -2]; return 0; }