牛牛种小——超级详细的N^2和N^3做法

这篇博客探讨了一道与图论和树的性质相关的数学问题。作者指出,树中每个节点的度数之和等于2*(n-1),其中n是节点数。问题在于,在保持这个条件的同时,如何在给定的节点度数和价值数组中找到最大的总价值。文章首先介绍了朴素的O(n^3)解决方案,然后通过转换为完全背包问题优化到O(n^2)的时间复杂度。作者提供了相应的C++代码实现,并解释了如何通过预分配节点度来确保所有节点都包含在树中。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意

①题意中的度指的是对于数中每个结点的边数和,实际上就是图中无向图结点的度
②根据树的特点,每个结点度至少为1,且n个结点的有n-1条边,每条边连接两个点,对这两个点都有一个度的贡献,所以树的度的和为 2*(n-1)
③问题转化为 对 n 个结点选择 1 ~ (n - 1) 的度数,在度数和为 2 * ( n - 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;
}


优化为O(n^2) 完全背包模型

现在考虑如何优化,其实上面这个问题是一个完全背包,有 n 个物品,每个物品都有各自的度数,和度数带来的价值f(i),物品可以任意选择,但是总共选的物品个数要为n(结点个数) ,物品体积之和(度数和)为2(n - 1),求最大价值。

简单的的完全背包只要枚举物品和体积就行时间复杂度是n^2是可以过的,所以可以考虑把上面的代码压缩掉一个维度,从完全背包对应过来,对度数(物品)的枚举和总的度数花费是一定要保留的,所以这里我们可以考虑把总共选的物品(结点个数)个数要为n压缩掉

如何压缩

之所以有结点个数这个维度,因为要保证这是一棵有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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值