题意

①题意中的度指的是对于数中每个结点的边数和,实际上就是图中无向图结点的度
②根据树的特点,每个结点度至少为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;
}