动态规划入门
动态规划入门
1.简单动态规划
动态规划问题的关键是找到一个合适,符合题意的状态,找出状态之间的转移关系。
A.数塔问题
题目描述
如图1所示为一个数字三角形。请编一个程序,计算从顶到底的某处的一条路径,使该路径所经过的数字总和最大。只要求输出总和。
1、一步可沿左斜线向下或右斜线向下走。
2、三角形行数小于等于
3、三角形中的数字为
测试数据通过键盘逐行输入,如上例数据应以如图2所示格式输入。
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
图1
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
图2
输入
第一行一个整数
接下来
输出
输出总和
样例输入
5
13
11 8
12 7 26
6 14 15 8
12 7 13 24 11
样例输出
86
思路
这是一道简单的动态规划题目,定义状态
根据题意,可以从当前格子向下或向右下走,即当前格子可以由上方的格子向下走或左上方的格子向右下走得到,所以当前状态是由上方和左上方两个状态转移而来,选择较大的一条路径,加上当前的权值即可。
代码
#include <bits/stdc++.h>
using namespace std;
int n, ans, a[1005][1005], dp[1005][1005];
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= i; j ++)
scanf("%d", &a[i][j]);
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= i; j ++) { // 三角形
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - 1]) + a[i][j]; // 状态转移方程
ans = max(ans, dp[i][j]); // 统计答案
}
printf("%d\n", ans);
return 0;
}
B.最长上升子序列
题目描述
输入一个长度为
输入
若干个整数
输出
这些整数的最长上升子序列的长度
样例输入
300 250 275 252 200 138 245
样例输出
max=2
思路
暴力思路
定义状态
初始值:
时间复杂度:
正解思路
考虑状态 lower_bound
来求解,最终的
时间复杂度:
代码
二分写法
#include <bits/stdc++.h>
using namespace std;
int a[100005], dp[100005], len, n;
int my_lower_bound(int x) { // 二分寻找第一个大于等于x的数
int l = 1, r = len, mid;
while (l <= r) {
mid = (l + r) >> 1;
if (dp[mid] >= x) r = mid - 1;
else l = mid + 1;
}
return l;
}
int main() {
while (scanf("%d", &a[++ n]) != EOF);
for (int i = 1; i <= n; i ++) {
if (a[i] > dp[len]) dp[++ len] = a[i];
else dp[my_lower_bound(a[i])] = a[i];
}
printf("max=%d\n", len);
return 0;
}
lower_bound
写法
#include <bits/stdc++.h>
using namespace std;
int a[100005], dp[100005], len, n;
int main() {
while (scanf("%d", &a[++ n]) != EOF);
for (int i = 1; i <= n; i ++) {
if (a[i] > dp[len]) dp[++ len] = a[i];
else *lower_bound(dp + 1, dp + len + 1, a[i]) = a[i]; // 返回第一个大于等于a[i]的数的地址
}
printf("max=%d\n", len);
return 0;
}
C.导弹拦截
题目描述
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
输入导弹依次飞来的高度,计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
输入格式
一行,若干个整数,中间由空格隔开。
输出格式
两行,每行一个整数,第一个数字表示这套系统最多能拦截多少导弹,第二个数字表示如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
样例输入
389 207 155 300 299 170 158 65
样例输出
6
2
思路
问题
最长不上升子序列:
定义状态 upper_bound
来求解,最终的
时间复杂度:
代码
#include <bits/stdc++.h>
using namespace std;
long long n = 1, a[100005];
long long dp1[100005], len1;
long long dp2[100005], len2;
int main() {
while (scanf("%lld", &a[n]) != EOF) n ++;
n --;
dp1[++ len1] = a[1];
for (int i = 2; i <= n; i ++) { // 最长不上升子序列
if (dp1[len1] >= a[i]) dp1[++ len1] = a[i];
else *upper_bound(dp1 + 1, dp1 + len1 + 1, a[i], greater<int>()) = a[i];
// upper_bound返回第一个大于a[i]的数, 加了greater<int>()后返回第一个小于a[i]的数
}
dp2[++ len2] = a[1];
for (int i = 2; i <= n; i ++) { // 最长上升子序列
if (dp2[len2] < a[i]) dp2[++ len2] = a[i];
else *lower_bound(dp2 + 1, dp2 + len2 + 1, a[i])= a[i];
}
printf("%lld\n%lld", len1, len2);
return 0;
}
D.最长公共子序列
题目描述
给定两个字符串序列
输入
共两行,分别为字符串序列x和y
输出
一行一个整数
样例输入
cnblogs
belong
样例输出
4
思路
定义状态
第一行表示当前位置的两个字符相同,可以从上一个字符的状态转移过来,答案加
时间复杂度:
空间复杂度:
代码
#include<bits/stdc++.h>
using namespace std;
int dp[1005][1005], ans, lena, lenb;
char a[1005], b[1005];
int main() {
scanf("%s%s", a + 1, b + 1);
lena = strlen(a + 1), lenb = strlen(b + 1);
for (int i = 1; i <= lena; i ++) {
for (int j = 1; j <= n; j ++) { // 转移
dp[i][j] = max(dp[i][j-1], dp[i-1][j]);
if (a[i] == b[j])
dp[i][j] = max(dp[i][j], dp[i - 1][j - 1] + 1);
ans = max(ans, dp[i][j]);
}
}
printf("%d\n", ans);
return 0;
}
E.编辑距离
题目描述
设A和B是两个字符串。我们要用最少的字符操作次数,将字符串A转换为学符串B。这里所说的字符操作共有三种:
(1)删除一个字符;
(2)插人一个字符;
(3)将一个字符改为另一个字符。
对任意的两个字符串A和B,计算出将字符串A变换为字符串B所用的最少字符操作次数。
输入
第1行为字符串A;第2行为字符串B;字符串A和B的长度均小于200。
输出
只有一个正整数,为最少字符操作次数。
样例输入
sfdqxbw
gfdgw
样例输出
4
思路
定义状态
第一行指当这两个字符相同时,最少次数即从
第二行指当前两个字符不相同,分三种情况。1.
初始值:
时间复杂度:
空间复杂度:
代码
#include <bits/stdc++.h>
using namespace std;
char stra[2005], strb[2005];
int lena, lenb, dp[2005][2005];
int main() {
scanf("%s%s", stra + 1, strb + 1);
lena = strlen(stra + 1), lenb = strlen(strb + 1);
for (int i = 0; i <= lena; i ++) // 初始值
dp[i][0] = i;
for (int i = 0; i <= lenb; i ++)
dp[0][i] = i;
for (int i = 1; i <= lena; i ++) {
for (int j = 1; j <= lenb; j ++) { // 转移
if (stra[i] == strb[j])
dp[i][j] = dp[i - 1][j - 1];
else dp[i][j] = min(dp[i - 1][j], min(dp[i][j - 1], dp[i - 1][j - 1])) + 1;
}
}
printf("%d\n", dp[lena][lenb]);
return 0;
}
2.背包类型动态规划
背包类型动态规划指给定一个背包容量
A.01背包
思路
01背包指每种物品只有1件,只能选择装入背包或不装入背包。
定义状态
第一种情况
第二种情况
其中加入这一件物品的状态
答案即为
时间复杂度:
空间复杂度:
代码
#include <bits/stdc++.h>
using namespace std;
int n, m, w[205], v[205], dp[205][205];
int main() {
scanf("%d%d", &m, &n);
for (int i = 1; i <= n; i ++)
scanf("%d%d", &w[i], &v[i]);
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= m; j ++) { // 转移
dp[i][j] = dp[i - 1][j];
dp[i][j] = max(dp[i][j], dp[i - 1][j - w[i]] + v[i]);
}
printf("%d\n", dp[n][m]);
return 0;
}
优化
这个思路需要开一个二维数组,空间可能不够,考虑优化成一维数组。
观察发现
时间复杂度:
空间复杂度:
优化后代码
#include <bits/stdc++.h>
using namespace std;
int n, m, w[205], v[205], dp[205];
int main() {
scanf("%d%d", &m, &n);
for (int i = 1; i <= n; i ++)
scanf("%d%d", &w[i], &v[i]);
for (int i = 1; i <= n; i ++)
for (int j = m; j >= w[i]; j --) // 注意循环顺序
dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
printf("%d\n", dp[m]);
return 0;
}
升级
题目描述
潜水员为了潜水要使用特殊的装备。他有一个带
例如:潜水员有5个气缸。每行三个数字为:氧,氮的(升)量和气缸的重量:
3 36 120
10 25 129
5 50 250
1 45 130
4 20 119
如果潜水员需要
你的任务就是计算潜水员为了完成他的工作需要的气缸的重量的最低值。
输入
第一行有2整数
第二行为整数
此后的k行,每行包括
输出
仅一行包含一个整数,为潜水员完成工作所需的气缸的重量总和的最低值。
样例输入
5 60
5
3 36 120
10 25 129
5 50 250
1 45 130
4 20 119
样例输出
249
思路
这是一个变形的01背包,重量有两维(氧气,氮气),把原
代码
#include <bits/stdc++.h>
using namespace std;
int m, n, k, a[1005], b[1005], c[1005], dp[1005][1005];
int main() {
scanf("%d%d%d", &m, &n, &k);
for (int i = 1; i <= k; i ++)
scanf("%d%d%d", &a[i], &b[i], &c[i]);
memset(dp, 0x3f, sizeof(dp));
dp[0][0] = 0;
for (int i = 1; i <= k; i ++)
for (int j = m; j >= 0; j --) // 倒序
for (int l = n; l >= 0; l --) { // 倒序
int x = max(0, j - a[i]), y = max(0, l - b[i]); // 边界,因为大于需求量也是可以的
dp[j][l] = min(dp[j][l], dp[x][y] + c[i]);
}
printf("%d\n", dp[m][n]);
return 0;
}
B.完全背包
思路
完全背包指每种物品有无数件,能装下的前提下可以无限装。
定义状态
第一行与01背包相同,为放不下第
第二行与01背包有区别,
时间复杂度:
空间复杂度:
代码
#include <bits/stdc++.h>
using namespace std;
int n, m, w[205], v[205], dp[205][205];
int main() {
scanf("%d%d", &m, &n);
for (int i = 1; i <= n; i ++)
scanf("%d%d", &w[i], &v[i]);
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= m; j ++) {
dp[i][j] = dp[i - 1][j];
dp[i][j] = max(dp[i][j], dp[i][j - w[i]] + v[i]); // 注意与01背包的不同
}
printf("%d\n", dp[n][m]);
return 0;
}
优化
和01背包一样,完全背包也可以压缩空间,但注意一点,完全背包的第二层循环需要正序循环,原因是状态转移方程有改变,当前状态需要从已经更新过的当前状态转移得到,而不是上一层状态,01背包倒序枚举是为了满足从上一层状态转移的需求。
时间复杂度:
空间复杂度:
优化后代码
#include <bits/stdc++.h>
using namespace std;
int n, m, w[205], v[205], dp[205];
int main() {
scanf("%d%d", &m, &n);
for (int i = 1; i <= n; i ++)
scanf("%d%d", &w[i], &v[i]);
for (int i = 1; i <= n; i ++)
for (int j = w[i]; j <= m; j ++) // 注意循环顺序
dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
printf("%d\n", dp[m]);
return 0;
}
C.多重背包
多重背包指每个物品有限定的个数,在不超过总重量的前提下使总价值最大。
思路
暴力思路
多次01背包,很好理解。在01背包外套一层循环。
时间复杂度:
空间复杂度:
优化思路
将每个数都拆成
时间复杂度:
空间复杂度:
代码
#include <bits/stdc++.h>
using namespace std;
int n, m, w[105], v[105], c[105], cnt;
int dp[1000005], a[1000005], b[1000005];
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++)
scanf("%d%d", &w[i], &v[i]);
for (int i = 1; i <= n; i ++)
scanf("%d", &c[i]);
for (int i = 1; i <= n; i ++) { // 拆分
int t = c[i], k = 1;
while (k <= t) {
cnt ++;
a[cnt] = w[i] * k; // 拆分装入
b[cnt] = v[i] * k; // 拆分装入
t -= k;
k <<= 1; // 1,2,4,8
}
if (t > 0) {
cnt ++; // 剩余 (7)
a[cnt] = w[i] * t;
b[cnt] = v[i] * t;
}
}
for (int i = 1; i <= cnt; i ++)
for (int j = m; j >= a[i]; j --) // 01 背包
dp[j] = max(dp[j], dp[j - a[i]] + b[i]);
printf("%lld\n", dp[m])
return 0;
}
D.混合背包
指01背包,完全背包,多重背包的结合体,把每个程序的循环掏出来合在一起就行了,01背包可以当特殊的多重背包处理,依然可以用二进制拆分。
E.分组背包
指物品分成了若干组,每组
思路
定义
很好理解,每次装物品从考虑到上一组的状态转移过来,不会有重复拿的问题,可以看出,方程和01背包的很想,只是多了一层枚举,加上即可,空间优化与01背包相同。优化空间时,注意循环顺序,
时间复杂度:
空见复杂度:
代码
#include <bits/stdc++.h>
using namespace std;
int n, m, w[1005], v[1005];
int dp[1005], z[105][1005];
int mx;
int main() {
scanf("%d%d", &m, &n);
for (int i = 1, x; i <= n; i ++) {
scanf("%d%d%d", &w[i], &v[i], &x);
z[x][++ z[x][0]] = i;
mx = max(mx, x);
}
for (int i = 1; i <= mx; i ++)
for (int j = m; j >= 0; j --)
for (int k = 1; k <= z[i][0]; k ++) // 注意循环顺序,k只能在最里面
if (j >= w[z[i][k]])
dp[j] = max(dp[j], dp[j - w[z[i][k]]] + v[z[i][k]]);
printf("%d\n", dp[m]);
return 0;
}
3.区间类型动态规划
区间动态规划一般解决的是区间类型的问题,先求解一个个小区间,再把小区间合并成大一点的区间,重复,得到更大的区间,重复,得到答案。
A.合并石子
题目描述
在一个操场上一排地摆放着
试设计一个程序,计算出将
输入
第
以下
输出
一个正整数,即最小得分。
样例输入
7
13
7
8
16
21
4
18
样例输出
239
思路
定义状态
其中
时间复杂度:
空间复杂度:
代码
#include <bits/stdc++.h>
#define sum(i, j) (cnt[j] - cnt[i - 1]) // 前缀和
using namespace std;
int n, dp[105][105], cnt[105];
int main() {
scanf("%d", &n);
memset(dp, 0x3f, sizeof(dp));
for (int i = 1, a; i <= n; i ++) {
scanf("%d", &a);
cnt[i] = cnt[i - 1] + a; // 前缀和
dp[i][i] = 0;
}
for (int len = 2; len <= n; len ++) {
for (int i = 1; i + len - 1 <= n; i ++) {
int j = i + len - 1;
for (int k = i; k < j; k ++) { // 状态转移
if (dp[i][j] > dp[i][k] + dp[k + 1][j] + sum(i, j))
dp[i][j] = dp[i][k] + dp[k + 1][j] + sum(i, j);
}
}
}
printf("%d\n", dp[1][n]); // 答案
return 0;
}
B.能量项链
题目描述
在 Mars 星球上,每个 Mars 人都随身佩带着一串能量项链。在项链上有
需要时,Mars 人就用吸盘夹住相邻的两颗珠子,通过聚合得到能量,直到项链上只剩下一颗珠子为止。显然,不同的聚合顺序得到的总能量是不同的,请你设计一个聚合顺序,使一串项链释放出的总能量最大。
例如:设
这一串项链可以得到最优值的一个聚合顺序所释放的总能量为:
输入格式
第一行是一个正整数
至于珠子的顺序,你可以这样确定:将项链放到桌面上,不要出现交叉,随意指定第一颗珠子,然后按顺时针方向确定其他珠子的顺序。
输出格式
一个正整数
样例输入
4
2 3 5 10
样例输出
710
思路
转移方程和上题类似,不同的是这道题在环上,把原数组复制一遍,变成
时间复杂度:
空间复杂度:
代码
#include <bits/stdc++.h>
using namespace std;
int n, ans, a[205], f[205][205];
int main() {
cin >> n;
for (int i = 1; i <= n; i ++) // 处理环
cin >> a[i], a[i + n] = a[i];
for (int len = 1; len <= 2 * n; len ++) // 2n
for (int i = 1; i + len - 1 <= 2 * n; i ++) {
int j = i + len - 1;
for (int k = i; k < j; k ++)
if (f[i][k] + f[k + 1][j] + a[i] * a[k + 1] * a[j + 1] > f[i][j])
f[i][j] = f[i][k] + f[k + 1][j] + a[i] * a[k + 1] * a[j + 1]; // 状态转移
}
for (int i = 1; i <= n + 1; i ++) // 要考虑所有环的情况
ans = max(ans, f[i][i + n - 1]);
cout << ans;
return 0;
}
4.树形动态规划
树形动态规划指在树上做动态规划。
A.没有上司的舞会
题目描述
某大学有
他们之间有从属关系,也就是说他们的关系就像一棵以校长为根的树,父结点就是子结点的直接上司。
现在有个周年庆宴会,宴会每邀请来一个职员都会增加一定的快乐指数
所以,请你编程计算,邀请哪些职员可以使快乐指数最大,求最大的快乐指数。
输入格式
输入的第一行是一个整数
第
第
输出格式
输出一行一个整数代表最大的快乐指数。
样例输入
7
1
1
1
1
1
1
1
1 3
2 3
6 4
7 4
4 5
3 5
样例输出
5
数据规模与约定
对于
思路
这是一道典型的树形动态规划。定义状态
第一行表示上司不去的状态为下属去的状态和下属不去的状态的最大值。第二行表示如果上司去了,下属就只能不去。
实现方式是
时间复杂度:
空间复杂度:
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 6005;
int ver[N << 1], nxt[N << 1], head[N], tot;
int n, ans, p[N], f[N][2], root;
bool rt[N];
void add(int x, int y) { // 前向星
ver[++ tot] = y;
nxt[tot] = head[x];
head[x] = tot;
}
void dfs(int x) {
for (int i = head[x]; i; i = nxt[i]) { // 枚举孩子
int y = ver[i];
dfs(y); // 处理孩子
f[x][1] += f[y][0]; // 状态转移方程
f[x][0] += max(f[y][0], f[y][1]);
}
f[x][1] += p[x]; // 如果去就加上自己的快乐指数
ans = max(f[x][0], f[x][1]); // 统计答案
}
int main(){
scanf("%d", &n);
for (int i = 1; i <= n; i ++)
scanf("%d", &p[i]);
for (int i = 1, l, k; i < n; i ++) {
scanf("%d%d", &l, &k);
add(k, l); // 加边
rt[l] = 1;
}
for (int i = 1; i <= n; i ++) // 找根
if (!rt[i])
root = i;
dfs(root); // dp
printf("%d\n", ans);
return 0;
}
B.战略游戏
题目背景
Bob 喜欢玩电脑游戏,特别是战略游戏。但是他经常无法找到快速玩过游戏的办法。现在他有个问题。
题目描述
他要建立一个古城堡,城堡中的路形成一棵无根树。他要在这棵树的结点上放置最少数目的士兵,使得这些士兵能瞭望到所有的路。
注意,某个士兵在一个结点上时,与该结点相连的所有边将都可以被瞭望到。
请你编一程序,给定一树,帮 Bob 计算出他需要放置最少的士兵。
输入格式
第一行一个整数
第二行至第
对于一个
输出格式
输出文件仅包含一个整数,为所求的最少的士兵数目。
样例输入
4
0 1 1
1 2 2 3
2 0
3 0
样例输出
1
数据规模与约定
对于全部的测试点,保证
思路
定义状态
第一行表示
时间复杂度:
空间复杂度:
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1505;
int ver[N << 1], nxt[N << 1], head[N], tot;
int n, f[N][2];
void add(int x, int y) { // 前向星
ver[++ tot] = y;
nxt[tot] = head[x];
head[x] = tot;
}
void dfs(int x, int fa) {
for (int i = head[x]; i; i = nxt[i]) { // 枚举孩子
int y = ver[i];
if (y == fa) continue; // 重边
dfs(y, x); // 处理孩子
f[x][0] += f[y][1]; //转移
f[x][1] += min(f[y][1], f[y][0]);
}
f[x][1] ++; // 自己放了,加上
}
int main(){
scanf("%d", &n);
for (int i = 1, id, k; i <= n; i ++) {
scanf("%d%d", &id, &k);
for (int j = 1, x; j <= k; j ++) {
scanf("%d", &x);
add(id + 1, x + 1); // 加边
add(x + 1, id + 1);
}
}
dfs(1, 0);
printf("%d\n", min(f[1][0], f[1][1])); // 答案
return 0;
}
C.选课
题目描述
在大学里每个学生,为了达到一定的学分,必须从很多课程里选择一些课程来学习,在课程里有些课程必须在某些课程之前学习,如高等数学总是在其它课程之前学习。现在有
输入格式
第一行有两个整数
接下来的
输出格式
只有一行,选
样例输入
7 4
2 2
0 1
0 4
2 1
7 1
7 6
2 2
样例输出
13
思路
这是一个树上的背包问题,定义
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 305;
int ver[N << 1], nxt[N << 1], head[N], tot;
int m, n, v[N], f[N][N], siz[N];
void add(int x, int y) { //前向星
ver[++ tot] = y;
nxt[tot] = head[x];
head[x] = tot;
}
void dfs(int x) {
f[x][1] = v[x]; // 初始值,子树中选一个就必选自己
for (int i = head[x]; i; i = nxt[i]) { // 枚举孩子
int y = ver[i]; // 获取孩子
dfs(y); // 处理孩子
for (int k = n; k >= 1; k --) // 倒序枚举,01背包
for (int j = 0; j < k; j ++) // 分配给这个孩子的容量
f[x][k] = max(f[x][k], f[x][k - j] + f[y][j]); // 取最优
}
}
int main(){
scanf("%d%d", &m, &n);
n ++; // 加了一个源点0
for (int i = 1, x; i <= m; i ++) {
scanf("%d%d", &x, &v[i]);
add(x, i); // 加边
}
dfs(0); // dp
printf("%d\n", f[0][n]); // 输出答案
return 0;
}
D.二叉苹果树
题目描述
有一棵苹果树,如果树枝有分叉,一定是分二叉(就是说没有只有一个儿子的结点)
这棵树共有
我们用一根树枝两端连接的结点的编号来描述一根树枝的位置。下面是一颗有
2 5
\ /
3 4
\ /
1
现在这颗树枝条太多了,需要剪枝。但是一些树枝上长有苹果。
给定需要保留的树枝数量,求出最多能留住多少苹果。
输入格式
第一行
接下来
输出格式
一个数,最多能留住的苹果的数量。
样例输入
5 2
1 3 1
1 4 10
2 3 20
3 5 20
样例输出
21
数据规模与约定
思路
和上道题类似,不过把点权换成了边权,定义
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 105;
int ver[N << 1], nxt[N << 1], head[N << 1], edge[N << 1], tot;
int n, q, f[N][N];
void add(int x, int y, int z) { // 前向星
ver[++ tot] = y;
nxt[tot] = head[x];
head[x] = tot;
edge[tot] = z;
}
void dfs(int x, int fa) {
for (int i = head[x]; i; i = nxt[i]) { // 枚举孩子
int y = ver[i]; // 获取孩子
if (y == fa) continue; // 重边
dfs(y, x); // 处理孩子
for (int k = q; k >= 1; k --) // 倒序枚举, 01背包
for (int j = 0; j < k; j ++) // 分配给孩子的边
f[x][k] = max(f[x][k], f[x][k - j - 1] + f[y][j] + edge[i]); // 注意这里的不同
// 为什么是 f[x][k - j - 1] 呢? 因为要保证(x,y)这条边被选
}
}
int main(){
scanf("%d%d", &n, &q);
for (int i = 1, x, y, z; i <= n - 1; i ++) {
scanf("%d%d%d", &x, &y, &z);
add(x, y, z); // 加边
add(y, x, z);
}
dfs(1, 0); // dp
printf("%d\n", f[1][q]); // 答案
return 0;
}
未完成:5.图上的动态规划,6.状态压缩动态规划,7.数位动态规划。
本文来自博客园,作者:maniubi,转载请注明原文链接:https://www.cnblogs.com/maniubi/p/17612885.html,orz
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)