蓝桥杯冲刺 - Lastweek - 你离省一仅剩一步之遥!!!(掌握【DP】冲刺国赛)
文章目录
- 💬前言
- 🎯week3
- 🌲day1
- 0-1背包
- 完全背包
- 多重背包
- 多重背包 II
- 分组背包
- 🌲day2
- 数字三角形 - 线性DP
- 1015. 摘花生 - 数字三角形
- 🌲day3
- 最长上升子序列 - 线性DP
- 1017. 怪盗基德的滑翔翼 - LIS
- 1014.登山 - LIS
- 最长公共子序列 - 线性DP
- 🌲day4
- 最短编辑距离 - 线性DP
- 编辑距离 - 线性DP
- 🌲day5
- 石子合并 - 区间DP
- 整数划分 - 计数DP
- 🌲day6
- 蒙德里安的梦想 - 状压DP
- 最短Hamilton路径
- 🌲day7
- 没有上司的舞会 - 树形DP
💬前言
💡本文以经典DP入手,带你走进DP的大门,感受DP的魅力
🔥🔥🔥DP是 重中之重\blue{重中之重}重中之重 ,它能决定你的最终名次
📌在比赛中DP是难点也是重点,最重要的是它的分值比重大
📌DP虽难但也有规律可循,有大量的例题模板,经典DP,考题往往会在基本理论的基础上进行变化
📌考场上要能准确看出是哪种类型的DP,就能快速入手尝试突破。
🏁🏁等你掌握DP时就可以自信的和你的对手说:什么档次敢和我写一样的DP题
如果对您有帮助的话还请动动小手 点赞👍,收藏⭐️,关注❤️
🎯week3
🌲day1
0-1背包
01背包问题
给定 n 堆石子以及一个由 k 个不同正整数构成的数字集合 S。
现在有两位玩家轮流操作,每次操作可以从任意一堆石子中拿取石子,每次拿取的石子数量必须包含于集合 S,最后无法进行操作的人视为失败。
问如果两人都采用最优策略,先手是否必胜。
输入格式
第一行包含整数 k,表示数字集合 S 中数字的个数。
第二行包含 k 个整数,其中第 i 个整数表示数字集合 S 中的第 i 个数 si。
第三行包含整数 n。
第四行包含 n 个整数,其中第 i 个整数表示第 i 堆石子的数量 hi。
输出格式
如果先手方必胜,则输出 Yes。
否则,输出 No。
数据范围
1≤n,k≤100,
1≤si,hi≤10000
输入样例:
2
2 5
3
2 4 7
输出样例:
Yes
背包串联:多重背包 拆分为 0-1背包
扩:谈钱购买时,消耗的体积就是物品的价值:v = w (还有其他变式)
2023-DP-先会朴素
#include <bits/stdc++.h>using namespace std;const int N = 1010;int f[N][N];
int n,m;
int v,w;int main()
{scanf("%d%d",&n,&m);for(int i = 1; i <= n; i++){scanf("%d%d", &v, &w);for(int j = 0; j <= m; j++)if(j >= v)f[i][j] = max(f[i - 1][j], f[i - 1][j - v] + w); //可放 可不放else f[i][j] = f[i - 1][j]; //放不下当前物品-只能不放}printf("%d", f[n][m]);return 0;
}
(优化原理 - 等式错位相减)
#include <iostream>
#include<cstdio>//scanf && printf
#include<algorithm>//maxusing namespace std;const int N = 1010;
int n,m;
int v,w;//可边输入边判断, 也可以存下每种物品的体积v[i]和价值w[i]
int f[N];//f[j] :体积为j时的最大价值
int main()
{scanf("%d%d", &n, &m);for(int i = 0; i < n; i++){scanf("%d%d",&v, &w); //边输入边转移for(int j = m; j >= v; j --)f[j] = max( f[j] , f[j - v] + w);}printf("%d", f[m]);return 0;
}
数组版
#include <iostream>
#include <algorithm>using namespace std;const int N = 1010;int n, m;
int v[N], w[N]; //存物品体积v[N],价值w[N]
int f[N];int main()
{cin >> n >> m;for (int i = 1; i <= n; i ++ ) scanf("%d%d", &v[i], w[i]);for (int i = 1; i <= n; i ++ )for (int j = m; j >= v[i]; j -- )f[j] = max(f[j], f[j - v[i]] + w[i]);printf("%d", f[m]);return 0;
}
完全背包
有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。
第 i 种物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 种物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000
0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
10
二维朴素
const int N = 1010;
int n,m;
int v[N],w[N];
int f[N][N];
int main()
{cin >> n >> m;for(int i = 1;i <= n;i++)scanf("%d%d",&v[i],&w[i]);for(int i = 1;i <= n;i++)for(int j = 0;j <= m;j++) //体积为j时选择放入的物品{f[i][j] = f[i-1][j]; //不能放时保存上一个值if(j >= v[i]) f[i][j] = max(f[i][j] , f[i][j - v[i]] + w[i]); //能放时,比较最优解}cout << f[n][m] << endl;return 0;
}
一维优化:
#include<iostream>
#include<algorithm>using namespace std;const int N = 1010;int n, m;
int f[N];int main()
{scanf("%d%d", &n, &m);for(int i = 1; i <= n; i++) {int v, w;scanf("%d%d", &v, &w);for(int j = v; j <= m; j++)f[j] = max(f[j], f[j - v] + w);}printf("%d", f[m]);return 0;
}
开数组先读入,再枚举计算
#include<isotream>using namespace std;const int N = 1010;
int n,m;
int v[N],w[N],f[N];int main()
{cin >> n >> m; //n种物品,容量m的背包for(int i = 1; i <= n; i++)scanf("%d%d", &v[i], &w[i]);for(int i = 1; i <= n; i++)for(int j = v[i]; j <= m; j++) //体积为j时选择放入的物品f[j] = max(f[j] , f[j - v[i]] + w[i]); //【好记:与01背包不同按从小到大枚举体积:得max_w】cout << f[m] << endl;return 0;
}
多重背包
有 N 种物品和一个容量是 V 的背包。
第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤100
0<vi,wi,si≤100
输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出样例:
10
一、状态表示:f[i][j]
1. 集合:从前i个物品中选,且总体积不超过j的所有方案的集合.
2. 属性:最大值二、状态计算:
1. 思想-----集合的划分
2. 集合划分依据:根据第i个物品有多少个来划分.含0个、含1个···含k个.
状态表示与完全背包朴素代码一样均为:
f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]);时间复杂度O(n∗v∗s)
最精简版-边读入边计算【思想:拆成0-1背包判断】
#include <iostream>using namespace std;const int N = 1010;int f[N];
int n, m;
int v, w, s;int main()
{scanf("%d%d", &n, &m);while(n--)//n之后没用,可以n-- , 也可for(int i = 1; i <= n; i++){scanf("%d%d%d", &v, &w, &s);for(int i = 1;i <= s; i++)//拆成s件相同价值的物品 --> s次0-1背包计算for(int j = m; j >= v ; j--)f[j] = max(f[j], f[j - v] + w);}printf("%d", f[m]);
}
朴素版
#include <iostream>
#include <algorithm>using namespace std;
const int N = 110;int v[N], w[N], s[N];
int f[N][N];
int n, m;int main(){cin >> n >> m;for(int i = 1; i <= n; i ++) cin >> v[i] >> w[i] >> s[i];for(int i = 1; i <= n; i ++){//枚举种类for(int j = 1; j <= m; j ++){//枚举体积for(int k = 0; k <= s[i]; k ++){//枚举件数if(j >= k * v[i]){//转移限制条件f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]);//取k件物品减对应k件物品价值}}}}cout << f[n][m] << endl;return 0;
}
多重背包 II
有 N 种物品和一个容量是 V 的背包。
第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N≤1000
0<V≤2000
0<vi,wi,si≤2000
提示:
本题考查多重背包的二进制优化方法。
输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出样例:
10
二进制优化 + 0-1背包模板
思想举例:
7 的二进制111 :3位选不选0/1
1 2 4可组合成 0-7
但取 s = 10 ,非2^n - 1
就用 s - 1 - 2 - 4 = 3 < 8 ,已拆分的1 2 4对应二进制:1 10 100 【剩下的3单独加入集合】1 2 4 3 -- >枚举 (全不选) 0 - 10 (全选) 优化完能解决: 枚举v[i]物品体积 * s[i]每种物品个数 * m体积限制 : 4e10
不开数组版-边读入边计算[模板]
a, b, s : 每种物品: 体积v 价值w 数量s
#include<iostream>
#include<cstdio>using namespace std;const int N = 12010, M = 2010;//N:拆分后物品件数最多12000 , M : 背包体积int n, m;
int v[N], w[N]; //逐一枚举最大是N*logS
int f[M]; // 背包体积 < M , f[j]: 体积为j时的最大价值int main()
{scanf("%d%d", &n, &m); //n件种类组合,m总体积int cnt = 0; //记录拆分后种类 ,最后 n = cntfor(int i = 1; i <= n; i++){int a, b, s;//【开局部变量的好处就是可以在不同作用域内有多个重名但不冲突】 scanf("%d%d%d", &a, &b, &s); //不能用v,w和数组重名!!! int k = 1; //(乘积用1):拆分初始项1 ,k *= 2 1 2 4 (1 10 100)...while(k <= s){cnt ++;v[cnt] = a * k; // 原来共 a * s 拆成 a * 1 + a * 2 + a * 4 .... w[cnt] = b * k;s -= k;k *= 2; //也可以 k <<= 1}if(s > 0)// 最后若非 2^n-1 , 取 s 与 (2^n-1) 的余数 ,如 10 ,1 2 4 ... 3 ,则最后一项取 3v,3w{cnt ++;v[cnt] = a * s; w[cnt] = b * s;}}n = cnt; //*每个拆分的背包占体积不同 -- 种类不同(变多) //拆项后转化成01背包一维优化for(int i = 1; i <= n ; i ++)for(int j = m ; j >= v[i]; j --)f[j] = max(f[j], f[j - v[i]] + w[i]);printf("%d", f[m]);return 0;
}
开数组版-存每种物品属性
a[N], b[N], s[N];每种物品: 体积v 价值w 数量s
#include<iostream>using namespace std;const int N = 12010, M = 2010;//N:拆分后物品件数最多12000 , M : 背包体积int n, m;
int v[N], w[N]; //逐一枚举最大是N*logS
int f[M]; // 背包体积 < M , f[j]: 体积为j时的最大价值
int a[N], b[N], s[N];//每种物品: 体积v 价值w 数量sint main()
{scanf("%d%d", &n, &m); //n件种类组合,m总体积int cnt = 0; //记录拆分后种类 ,最后 n = cntfor(int i = 1;i <= n;i++) scanf("%d%d%d", &a[i], &b[i], &s[i]); //不能用v,w和数组重名!!!for(int i = 1;i <= n;i++){int k = 1; //(乘积用1):拆分初始项1 ,k *= 2 1 2 4 (1 10 100)...while(k <= s[i]){cnt ++ ;v[cnt] = a[i] * k; // 原来共 a * s 拆成 a * 1 + a * 2 + a * 4 .... w[cnt] = b[i] * k;s[i] -= k;k *= 2; //也可以 k <<= 1}if(s[i] > 0)// 最后若非 2^n-1 , 取 s 与 (2^n-1) 的余数 ,如 10 ,1 2 4 ... 3 ,则最后一项取 3v,3w{cnt ++ ;v[cnt] = a[i] * s[i]; w[cnt] = b[i] * s[i];}}n = cnt; //*每个拆分的背包占体积不同 -- 种类不同(变多) //拆项后转化成01背包一维优化for(int i = 1;i <= n ;i ++)for(int j = m ;j >= v[i];j --)f[j] = max(f[j],f[j-v[i]] + w[i]);printf("%d", f[m]);return 0;
}
分组背包
有 N 组物品和一个容量是 V 的背包。
每组物品有若干个,同一组内的物品最多只能选一个。
每件物品的体积是 vij,价值是 wij,其中 i 是组号,j 是组内编号。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行有两个整数 N,V,用空格隔开,分别表示物品组数和背包容量。
接下来有 N 组数据:
每组数据第一行有一个整数 Si,表示第 i 个物品组的物品数量;
每组数据接下来有 Si 行,每行有两个整数 vij,wij,用空格隔开,分别表示第 i 个物品组的第 j 个物品的体积和价值;
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤100
0<Si≤100
0<vij,wij≤100
输入样例
3 5
2
1 2
2 4
1
3 4
1
4 5
输出样例:
8
f[j]:只从前i组物品中选, 且总体积不超过j的选法
不选:f[j] , 选:f[j - v[i][k]] + w[i][k] : 比较取max继续转移下一个状态
#include <iostream>
#include <algorithm>using namespace std;const int N = 110;int n, m;
int v[N][N], w[N][N], s[N]; //二维存入物品体积、价值 :v[i][k] (第i组第k件物品体积)
int f[N]; //f[j] : 只从前i组物品中选, 且总体积不超过j的选法int main()
{scanf("%d%d", &n, &m);//依题读入for (int i = 0; i < n; i ++ ){scanf("%d", &s[i]);//存每组物品数量for (int j = 0; j < s[i]; j ++ )//读入每组物品:下标从0开始scanf("%d%d", &v[i][j], &w[i][j]);}for (int i = 0; i < n; i ++ )for (int j = m; j >= 0; j -- ) //0-1背包模板 【注意写法:k在下面枚举,此时下标还无法表示:只能写j >= 0(且j与k枚举顺序不能变)】for (int k = 0; k < s[i]; k ++ ) //每组物品下标从0开始if (v[i][k] <= j) //条件限制:能放的下此物品 v[i][k] : 第i组的第k件物品f[j] = max(f[j], f[j - v[i][k]] + w[i][k]); //选或不选printf("%d", f[m]);return 0;
}
🌲day2
数字三角形 - 线性DP
给定一个如下图所示的数字三角形,从顶部出发,在每一结点可以选择移动至其左下方的结点或移动至其右下方的结点,一直走到底层,要求找出一条路径,使路径上的数字的和最大。
73 88 1 02 7 4 44 5 2 6 5
输入格式
第一行包含整数 n,表示数字三角形的层数。
接下来 n 行,每行包含若干整数,其中第 i 行表示数字三角形第 i 层包含的整数。
输出格式
输出一个整数,表示最大的路径数字和。
数据范围
1≤n≤500,
−10000≤三角形中的整数≤10000
输入样例:
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
输出样例:
30
思路:
路径从起点走到终点的距离 == 终点走回起点的距离
逆序从最后一行开始走a[n][i] -->a[1][1] ,只需要一维:最终max都转移到f[1] : 以第n行第i列走到[1,1]的最大值max
往上转移:转移到上层[i][j] , 则下层相对于上层的下标为[i + 1][j], [i + 1][j + 1]
i用循环等效对应,f[j]一维枚举计算[j]与[j+1]:最后一轮i = 1,最终 f[1] 存转移到 a[1][1] 的max
朴素版
#include <iostream>
#include <algorithm>using namespace std;const int N = 510, INF = 1e9;int n;
int a[N][N];//存三角形
int f[N][N];//f[n][i] : 到第n个点的路径之和maxint main()
{scanf("%d", &n);for (int i = 1; i <= n; i ++ ) //dp用到下标i - 1 : 从1开始for (int j = 1; j <= i; j ++ )scanf("%d", &a[i][j]);//i = 1更准确, 0当然也可以(初始-INF需包括三角形及其边界)for (int i = 1; i <= n; i ++ )//下三角形 //【 因为题目n∈[-1e4,1e4], 存在负数 】, 所以应该将两边也设为-INFfor (int j = 0; j <= i + 1; j ++ )f[i][j] = -INF;//求最大边界不影响 取负无穷:-INFf[1][1] = a[1][1];//下标不越界:初始起点需赋值for (int i = 2; i <= n; i ++ )//2开始for (int j = 1; j <= i; j ++ )f[i][j] = max(f[i - 1][j - 1], f[i - 1][j]) + a[i][j];//选取上一个状态大的 + 此状态值//等效f[i][j] = max(f[i - 1][j - 1] + a[i][j], f[i - 1][j] + a[i][j]);int res = -INF;//求最大, 初始负无穷:-INFfor (int i = 1; i <= n; i ++ ) res = max(res, f[n][i]); //最后一行都是最终状态:选取maxprintf("%d\n", res);return 0;
}
从下往上优化:
#include<iostream>
#include<cstdio>
#include<algorithm>using namespace std;const int N = 1010;int f[N];
int a[N][N];int main()
{int n;scanf("%d", &n);for(int i = 1; i <= n; i++)//读入三角形, 为了不判断边界:从1开始for(int j = 1; j <= i; j++)scanf("%d", &a[i][j]);for(int i = n; i >= 1; i--) //推导后的一维优化:i=n 逆序从最后一行开始, j正序 : 从最后一行走到顶【顶只有1个】for(int j = 1; j <= i; j++)f[j] = max(f[j], f[j + 1]) + a[i][j];//往上走:转移下标为j和j + 1printf("%d", f[1]);return 0;
}
1015. 摘花生 - 数字三角形
Hello Kitty想摘点花生送给她喜欢的米老鼠。
她来到一片有网格状道路的矩形花生地(如下图),从西北角进去,东南角出来。
地里每个道路的交叉点上都有种着一株花生苗,上面有若干颗花生,经过一株花生苗就能摘走该它上面所有的花生。
Hello Kitty只能向东或向南走,不能向西或向北走。
问Hello Kitty最多能够摘到多少颗花生。

输入格式
第一行是一个整数T,代表一共有多少组数据。
接下来是T组数据。
每组数据的第一行是两个整数,分别代表花生苗的行数R和列数 C。
每组数据的接下来R行数据,从北向南依次描述每行花生苗的情况。每行数据有C个整数,按从西向东的顺序描述了该行每株花生苗上的花生数目M。
输出格式
对每组输入数据,输出一行,内容为Hello Kitty能摘到得最多的花生颗数。
数据范围
1≤T≤100
1≤R,C≤100,
0≤M≤1000
输入样例:
2
2 2
1 1
3 4
2 3
2 3 4
1 6 5
输出样例:
8
16
#include <iostream>
#include <algorithm> //需记库
using namespace std;const int N = 105;int n,m;
int w[N][N],f[N][N];int main(){int T;scanf("%d",&T);while(T--){scanf("%d%d",&n,&m);for(int i = 1;i <= n;i++)for(int j = 1;j <= m;j++)scanf("%d",&w[i][j]);for(int i = 1;i <= n;i++)for(int j = 1;j <= m;j++)f[i][j] = max(f[i-1][j],f[i][j-1])+w[i][j];printf("%d\n",f[n][m]);}return 0;
}
🌲day3
最长上升子序列 - 线性DP
给定一个长度为 N 的数列,求数值严格单调递增的子序列的长度最长是多少。
输入格式
第一行包含整数 N。
第二行包含 N 个整数,表示完整序列。
输出格式
输出一个整数,表示最大长度。
数据范围
1≤N≤1000,
−10910^9109≤数列中的数≤10910^9109
输入样例:
7
3 1 2 1 8 5 6
输出样例:
4
集合划分:f[i]为以a[i]结尾的最长上升子序列
#include <iostream>
#include <algorithm>using namespace std;const int N = 1010;int n;
int a[N];
int f[N];int main()
{scanf("%d", &n);for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);int res = 0;for (int i = 1; i <= n; i ++ ){f[i] = 1;//以i结尾的子序列:初始长度为本身 = 1for (int j = 1; j < i; j ++ )if (a[i] > a[j])//以i结尾子序列 = 以j结尾的子序列 + 1f[i] = max(f[i], f[j] + 1);res = max(res, f[i]);}printf("%d", res);return 0;
}
1017. 怪盗基德的滑翔翼 - LIS
怪盗基德是一个充满传奇色彩的怪盗,专门以珠宝为目标的超级盗窃犯。
而他最为突出的地方,就是他每次都能逃脱中村警部的重重围堵,而这也很大程度上是多亏了他随身携带的便于操作的滑翔翼。
有一天,怪盗基德像往常一样偷走了一颗珍贵的钻石,不料却被柯南小朋友识破了伪装,而他的滑翔翼的动力装置也被柯南踢出的足球破坏了。
不得已,怪盗基德只能操作受损的滑翔翼逃脱。
假设城市中一共有N幢建筑排成一条线,每幢建筑的高度各不相同。
初始时,怪盗基德可以在任何一幢建筑的顶端。
他可以选择一个方向逃跑,但是不能中途改变方向(因为中森警部会在后面追击)。
因为滑翔翼动力装置受损,他只能往下滑行(即:只能从较高的建筑滑翔到较低的建筑)。
他希望尽可能多地经过不同建筑的顶部,这样可以减缓下降时的冲击力,减少受伤的可能性。
请问,他最多可以经过多少幢不同建筑的顶部(包含初始时的建筑)?
输入格式
输入数据第一行是一个整数K,代表有K组测试数据。
每组测试数据包含两行:第一行是一个整数N,代表有N幢建筑。第二行包含N个不同的整数,每一个对应一幢建筑的高度h,按照建筑的排列顺序给出。
输出格式
对于每一组测试数据,输出一行,包含一个整数,代表怪盗基德最多可以经过的建筑数量。
数据范围
1≤K≤100,
1≤N≤100,
0<h<10000
输入样例:
3
8
300 207 155 299 298 170 158 65
8
65 158 170 298 299 155 207 300
10
2 1 3 4 5 6 7 8 9 10
输出样例:
6
6
9

方向确定就只能往一个方向跳(左/右)
如图:起点a[i] ,最长距离就是以a[i] 结尾的最长上升子序列
【图形:先上升再下降(则右边往左看为逆LIS ,左<—右 ,逆序LIS (起点n ,i- -,j- -))
简记:(正向+反向求解LIS ,比较得max)逆序: 起点n ,i--,j-- ,i,j意义与正向一样不变//不用res=0, 正反方向取max for(int i = n; i ;i--) //i > 0{f[i] = 1;for(int j = n;j > i;j --)if(a[i] > a[j])f[i] = max(f[i] , f[j] + 1); res = max(res,f[i]); //算出一个f[i]就比较一次 }
#include<bits/stdc++.h>#include<algorithm>
using namespace std;const int N = 1010;int n,T;
int a[N],f[N];
int main()
{int T;scanf("%d",&T);while(T--){scanf("%d",&n);for(int i = 1;i <= n;i++) scanf("%d",&a[i]); //正向求解LISint res = 0;for(int i = 1;i <= n;i++){f[i] = 1;for(int j = 1;j < i;j ++)if(a[j] < a[i])f[i] = max(f[i] , f[j] + 1); res = max(res,f[i]); //算出一个f[i]就比较一次 }//不用res=0, 正反方向取max for(int i = n; i ;i--) //i > 0{f[i] = 1;for(int j = n;j > i;j --)if(a[i] > a[j])f[i] = max(f[i] , f[j] + 1); res = max(res,f[i]); //算出一个f[i]就比较一次 }printf("%d\n",res); }return 0;
}
1014.登山 - LIS
题目描述:
五一到了,ACM队组织大家去登山观光,队员们发现山上一个有N个景点,并且决定按照顺序来浏览这些景点,即每次所浏览景点的编号都要大于前一个浏览景点的编号。
同时队员们还有另一个登山习惯,就是不连续浏览海拔相同的两个景点,并且一旦开始下山,就不再向上走了。
队员们希望在满足上面条件的同时,尽可能多的浏览景点,你能帮他们找出最多可能浏览的景点数么?
输入格式
第一行包含整数N,表示景点数量。
第二行包含N个整数,表示每个景点的海拔。
输出格式
输出一个整数,表示最多能浏览的景点数。
数据范围
2≤N≤1000
输入样例:
8
186 186 150 200 160 130 197 220
输出样例:
4

按编号递增顺序浏览 --> 必须是子序列
先严格上升再严格下降
一旦开始下降就不能上升了
分类:以a[k]为极值点(转折)的子序列的max(正+反-1:有一个共同重叠点)
简记: a[k]的max:上升存f[i],下降存g[i] ,res = max(res , f[i] + g[i] - 1)
#include<bits/stdc++.h>#include<algorithm>
using namespace std;const int N = 110;int n,T;
int a[N];
int g[N],f[N];
int main()
{scanf("%d",&n);for(int i = 1;i <= n;i++) scanf("%d",&a[i]); //正向求解LIS存到f[i] for(int i = 1;i <= n;i++){f[i] = 1;for(int j = 1;j < i;j ++)if(a[j] < a[i])f[i] = max(f[i] , f[j] + 1); }//反向求解LIS 存到g[i] for(int i = n;i;i--){g[i] = 1;for(int j = n;j > i;j--)if(a[i] > a[j])g[i] = max(g[i], g[j] + 1);} int res = 0;for(int i = 1;i <= n;i++) res = max(res , f[i] + g[i] - 1);printf("%d\n",res); return 0;
}
最长公共子序列 - 线性DP
给定两个长度分别为 N 和 M 的字符串 A 和 B,求既是 A 的子序列又是 B 的子序列的字符串长度最长是多少。
输入格式
第一行包含两个整数 N 和 M。
第二行包含一个长度为 N 的字符串,表示字符串 A。
第三行包含一个长度为 M 的字符串,表示字符串 B。
字符串均由小写字母构成。
输出格式
输出一个整数,表示最大长度。
数据范围
1≤N,M≤1000
输入样例:
4 5
acbd
abedc
输出样例:
3

#include <iostream>
#include <algorithm>using namespace std;const int N = 1010;int n, m;
char a[N], b[N];
int f[N][N];int main()
{scanf("%d%d", &n, &m);scanf("%s%s", a + 1, b + 1);//转移方程用到下标1,从1开始for (int i = 1; i <= n; i ++ )for (int j = 1; j <= m; j ++ ){f[i][j] = max(f[i - 1][j], f[i][j - 1]);//不相等有一个指针向前移动,i或j, 选取转移后的maxif (a[i] == b[j]) f[i][j] = max(f[i][j], f[i - 1][j - 1] + 1);//两个字符相等,双指针继续判断下一位//else f[i][j] = max(f[i - 1][j] , f[i][j - 1]); //按逻辑写这里 }printf("%d\n", f[n][m]);return 0;
}
🌲day4
最短编辑距离 - 线性DP
给定两个字符串 A 和 B,现在要将 A 经过若干操作变为 B,可进行的操作有:
删除–将字符串 A 中的某个字符删除。
插入–在字符串 A 的某个位置插入某个字符。
替换–将字符串 A 中的某个字符替换为另一个字符。
现在请你求出,将 A 变为 B 至少需要进行多少次操作。
输入格式
第一行包含整数 n,表示字符串 A 的长度。
第二行包含一个长度为 n 的字符串 A。
第三行包含整数 m,表示字符串 B 的长度。
第四行包含一个长度为 m 的字符串 B。
字符串中均只包含大小写字母。
输出格式
输出一个整数,表示最少操作次数。
数据范围
1≤n,m≤1000
输入样例:
10
AGTCTGACGC
11
AGTAAGTAGGC
输出样例:
4
题意: 每次操作可改变一个元素[增 删 改], 求使得a[]与b[]相等的最少操作次数
集合表示 :f[i][j]: a1......ai与b1......bj相等匹配a_1 ...... a_i 与 b_1 ...... b_j 相等匹配a1......ai与b1......bj相等匹配
状态划分 : 增 删 改
增:a[i]与b[j]中,a的前i个与b的前j−1个匹配,a需增添一个与b[j]相等的元素,对应f[i−1][j]+1a[i]与b[j]中, a的前i个与b的前j-1个匹配,a需增添一个与b[j]相等的元素, 对应f[i - 1][j] + 1a[i]与b[j]中,a的前i个与b的前j−1个匹配,a需增添一个与b[j]相等的元素,对应f[i−1][j]+1
删:$a[i]与b[j]中,a的前i-1个与b的前j个匹配,需删除a[i], 对应f[i][j - 1] + 1 $
改:【分两种情况 加0 或 加1】
~ ① a[i]与b[j]中,a的前i−1个与b的前j−1个匹配,需修改a[i],使得a[i]==b[j],对应f[i−1][j−1]+1~a[i]与b[j]中,a的前i-1个与b的前j-1个匹配,需修改a[i],使得a[i] == b[j], 对应f[i-1][j-1] + 1 a[i]与b[j]中,a的前i−1个与b的前j−1个匹配,需修改a[i],使得a[i]==b[j],对应f[i−1][j−1]+1
~ ② a[i]与b[j]中,a的前i−1个与b的前j−1个匹配,且a[i]==b[j]则无需操作,对应f[i−1][j−1]+0~a[i]与b[j]中,a的前i-1个与b的前j-1个匹配,且a[i] == b[j] 则无需操作, 对应f[i-1][j-1] + 0 a[i]与b[j]中,a的前i−1个与b的前j−1个匹配,且a[i]==b[j]则无需操作,对应f[i−1][j−1]+0
边界:f[0][i]:a增加i次变成b,f[i][0]:a删除i次变成b~f[0][i]:a增加i次变成b, f[i][0]:a删除i次变成b f[0][i]:a增加i次变成b,f[i][0]:a删除i次变成b
#include <iostream>
#include <algorithm>using namespace std;const int N = 1010;int n, m; //strlen(a), strlen(b)
char a[N], b[N];
int f[N][N];int main()
{scanf("%d%s", &n, a + 1); //【转移方程涉及 i - 1为了省去边界判断, 最好初始化从1开始】scanf("%d%s", &m, b + 1); //[否则影响如求min碰到边界0则被边界更新为0,出错]//边界for (int i = 0; i <= m; i ++ ) f[0][i] = i; //a前0个字符与b的前i个字符匹配:需要添加i次for (int i = 0; i <= n; i ++ ) f[i][0] = i; //a前i个字符与b的前0个字符匹配:需要删除i次for (int i = 1; i <= n; i ++ )for (int j = 1; j <= m; j ++ ){f[i][j] = min(f[i - 1][j] + 1, f[i][j - 1] + 1); //增, 删//if (a[i] == b[j]) f[i][j] = min(f[i][j], f[i - 1][j - 1]); //else f[i][j] = min(f[i][j], f[i - 1][j - 1] + 1); f[i][j] = min(f[i][j], f[i - 1][j - 1] + (a[i] != b[j]) ); //改的两种情况简写:+(表达式返回值)}printf("%d\n", f[n][m]); //把a的前n个字母变成b的前m个字母return 0;
}
编辑距离 - 线性DP
给定 n 个长度不超过 10 的字符串以及 m 次询问,每次询问给出一个字符串和一个操作次数上限。
对于每次询问,请你求出给定的 n 个字符串中有多少个字符串可以在上限操作次数内经过操作变成询问给出的字符串。
每个对字符串进行的单个字符的插入、删除或替换算作一次操作。
输入格式
第一行包含两个整数 n 和 m。
接下来 n 行,每行包含一个字符串,表示给定的字符串。
再接下来 m 行,每行包含一个字符串和一个整数,表示一次询问。
字符串中只包含小写字母,且长度均不超过 10。
输出格式
输出共 m 行,每行输出一个整数作为结果,表示一次询问中满足条件的字符串个数。
数据范围
1≤n,m≤1000,
输入样例:
3 2
abc
acd
bcd
ab 1
acbd 2
输出样例:
1
3
多次求最短编辑距离【封装】
思路: 求出最短编辑距离 判断 是否不超过上限limit :
if(编辑距离<=limit)res++;~if(编辑距离 <= limit) res ++; if(编辑距离<=limit)res++;
#include <iostream>
#include <algorithm>
#include <string.h>using namespace std;const int N = 15, M = 1010;int n, m;
int f[N][N];
char str[M][N];int edit_distance(char a[], char b[]) //求a[] 变成 b[] 的最少操作次数
{int la = strlen(a + 1), lb = strlen(b + 1); //注意首地址从1开始!!!for (int i = 0; i <= lb; i ++ ) f[0][i] = i;for (int i = 0; i <= la; i ++ ) f[i][0] = i;for (int i = 1; i <= la; i ++ )for (int j = 1; j <= lb; j ++ ){f[i][j] = min(f[i - 1][j] + 1, f[i][j - 1] + 1); //增, 删f[i][j] = min(f[i][j], f[i - 1][j - 1] + (a[i] != b[j])); //改的两种情况-精妙简写}return f[la][lb];
}int main()
{scanf("%d%d", &n, &m);for (int i = 0; i < n; i ++ ) scanf("%s", str[i] + 1); //每行从1开始 存入awhile (m -- ) //每轮询问:【str[][]中有多少个a满足操作次数 <= limit变成b】{char s[N];int limit;scanf("%s%d", s + 1, &limit); //读入b 和 操作次数限制limit int res = 0; for (int i = 0; i < n; i ++ )if (edit_distance(str[i], s) <= limit) //【注意传入从0开始!!对应函数内部才为从1开始取长度】res ++ ;printf("%d\n", res);}return 0;
}
🌲day5
石子合并 - 区间DP
设有 N 堆石子排成一排,其编号为 1,2,3,…,N。
每堆石子有一定的质量,可以用一个整数来描述,现在要将这 N 堆石子合并成为一堆。
每次只能合并相邻的两堆,合并的代价为这两堆石子的质量之和,合并后与这两堆石子相邻的石子将和新堆相邻,合并时由于选择的顺序不同,合并的总代价也不相同。
例如有 4 堆石子分别为 1 3 5 2, 我们可以先合并 1、2 堆,代价为 4,得到 4 5 2, 又合并 1,2 堆,代价为 9,得到 9 2 ,再合并得到 11,总代价为 4+9+11=24;
如果第二步是先合并 2,3 堆,则代价为 7,得到 4 7,最后一次合并代价为 11,总代价为 4+7+11=22。
问题是:找出一种合理的方法,使总的代价最小,输出最小代价。
输入格式
第一行一个数 N 表示石子的堆数 N。
第二行 N 个数,表示每堆石子的质量(均不超过 1000)。
输出格式
输出一个整数,表示最小代价。
数据范围
1≤N≤300
输入样例:
4
1 3 5 2
输出样例:
22
限制:每次只能合并相邻的两堆 ==> 根据限制划分:
第k堆为最后一次合并: f[l][k]+f[k+1][r]+s[r]−s[l−1];f[l][k] + f[k + 1][r] + s[r] - s[l - 1];f[l][k]+f[k+1][r]+s[r]−s[l−1];(区间和:代价用前缀和计算)

#include <iostream>
#include <algorithm>using namespace std;const int N = 310;int n;
int s[N];
int f[N][N];int main()
{scanf("%d", &n);for (int i = 1; i <= n; i ++ )//读入 , 初始化前缀和{scanf("%d", &s[i]);s[i] += s[i - 1];}//【区间枚举】for (int len = 2; len <= n; len ++ )//枚举合并的区间长度 【区间长度是1则不需要代价, 直接从2开始枚举】for (int i = 1; i + len - 1 <= n; i ++ )//枚举起点, 最后一个位置 <= n()不越界{int l = i, r = i + len - 1;f[l][r] = 1e8;//求最小, 初始需超过max边界值 (未初始化则全局为0,算出全为0...)for (int k = l; k < r; k ++ )f[l][r] = min(f[l][r], f[l][k] + f[k + 1][r] + s[r] - s[l - 1]);}//先不管最后一次合并的代价, 则代价转移为k点printf("%d\n", f[1][n]);return 0;
}
整数划分 - 计数DP
一个正整数 n 可以表示成若干个正整数之和,形如:n=n1+n2+…+nkn=n_1+n_2+…+n_kn=n1+n2+…+nk,其中 n1≥n2≥…≥nk,k≥1n_1≥n_2≥…≥n_k,~k≥1n1≥n2≥…≥nk, k≥1。
我们将这样的一种表示称为正整数 n 的一种划分。
现在给定一个正整数 n,请你求出 n 共有多少种不同的划分方法。
输入格式
共一行,包含一个整数 n。
输出格式
共一行,包含一个整数,表示总划分数量。
由于答案可能很大,输出结果请对 109+710^9+7109+7 取模。
数据范围
1≤n≤1000
输入样例:
5
输出样例:
7
y总
佬の思路:
把1,2,3, … n分别看做n个物体的体积,问恰好能装满总体积为n的背包的总方案数 (枚举这n个物体均无数量限制,用完全背包模板)
①完全背包解法
状态表示:
f[i][j]表示只从1~i中选,且总和等于j的方案数
状态转移方程:
f[i][j] = f[i - 1][j] + f[i][j - i];
同完全背包可以一维优化(去掉i):
f[j] = f[j] + f[j - i]
#include <iostream>
#include <algorithm>using namespace std;const int N = 1010, mod = 1e9 + 7;int n;
int f[N];int main()
{cin >> n;f[0] = 1;for (int i = 1; i <= n; i ++ )for (int j = i; j <= n; j ++ )//从i开始正序f[j] = (f[j] + f[j - i]) % mod; //【不懂推就记住】可以由上一个状态等价转移cout << f[n] << endl;return 0;
}
②其他算法
状态表示:
f[i][j]表示总和为i,总个数为j的方案数
状态转移方程:
f[i][j] = f[i - 1][j - 1] + f[i - j][j];
#include <iostream>
#include <algorithm>using namespace std;const int N = 1010, mod = 1e9 + 7;int n;
int f[N][N];int main()
{cin >> n;f[1][1] = 1;for (int i = 2; i <= n; i ++ )for (int j = 1; j <= i; j ++ )f[i][j] = (f[i - 1][j - 1] + f[i - j][j]) % mod;int res = 0;for (int i = 1; i <= n; i ++ ) res = (res + f[n][i]) % mod;cout << res << endl;return 0;
}
🌲day6
蒙德里安的梦想 - 状压DP
求把 N×M 的棋盘分割成若干个 1×2 的长方形,有多少种方案。
例如当 N=2,M=4 时,共有 5 种方案。当 N=2,M=3 时,共有 3 种方案。
如下图所示:

输入格式
输入包含多组测试用例。
每组测试用例占一行,包含两个整数 N 和 M。
当输入用例 N=0,M=0 时,表示输入终止,且该用例无需处理。
输出格式
每个测试用例输出一个结果,每个结果占一行。
数据范围
1≤N,M≤11
输入样例:
1 2
1 3
1 4
2 2
2 3
2 4
2 11
4 11
0 0
输出样例:
1
0
1
2
3
5
144
51205
能从k转移到j(即能从前一个状态转移过来):需满足
①(j & k) == 0没有冲突:横着放占两格:相邻状态转移不能为同一行
②j | k不存在连续奇数个0, 剩下的0能够放下竖着的两格才能填满

朴素写法,1000ms
#include <cstring>
#include <iostream>
#include <algorithm>using namespace std;const int N = 12, M = 1 << N;int n, m;
long long f[N][M]; //注意枚举种类很多开LL
bool st[M]; //当前这一列所有的空着的【连续的0是否都为偶数:是否合法】int main() //核心:枚举横着放, 再空位都放竖的(代码枚举横放)
{while (cin >> n >> m, n || m) //n, m都为0停止{for (int i = 0; i < 1 << n; i ++ ) // i < 2^n 枚举所有方案:每行格子选或不选 0或1{int cnt = 0;//统计当前这一段连续0的个数【0为空着的位置】st[i] = true;//假设此方案可行truefor (int j = 0; j < n; j ++ ) //每轮i共选n位(二进制数), 取i的二进制每位判断选或不选 if (i >> j & 1) //i的此位选择1:选择放横的判断i的二进制的j - 1位是否为1{if (cnt & 1) st[i] = false; //前面有连续的奇数个0, 不能成功转移, 此状态falsecnt = 0; //重新计数}else cnt ++ ; //不选为0 if (cnt & 1) st[i] = false; //最后一段连续个0,若是奇数个0,状态转移失败:【剩下的位置放竖的,必须剩余偶数个0】 }memset(f, 0, sizeof f); //f每轮需要重新置0【多轮输入】f[0][0] = 1; //不可能f[-1][0]转移过来, 空集也代表一种方案【不用填:变相填满】for (int i = 1; i <= m; i ++ ) //枚举所有列for (int j = 0; j < 1 << n; j ++ ) //枚举此列所有选或不选状态for (int k = 0; k < 1 << n; k ++ ) //枚举前一列的所有二进制排列状态if ((j & k) == 0 && st[j | k]) //j为转移后的状态, k为转移前的状态f[i][j] += f[i - 1][k]; //加上之前能转移的方案数cout << f[m][0] << endl; //能转移到m列,且f[m][0]不会"捅"出来, 才是一个合法方案}return 0;
}
去除无效状态的优化写法,230ms
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>using namespace std;typedef long long LL;const int N = 12, M = 1 << N; //枚举所有状态:2^n 【二进制位选或不选】int n, m;
LL f[N][M]; //种类很多 LL
vector<int> state[M]; //存能转移到state[]的状态(存前一个状态)
bool st[M]; //st预处理无效状态 : 所有二进制选择方案的状态:是否合法int main()
{while (cin >> n >> m, n || m) //n, m都为0停止{for (int i = 0; i < 1 << n; i ++ ){int cnt = 0; //统计当前方案每一段连续0的个数【0为空着的位置】bool is_valid = true; //此方案是否有效for (int j = 0; j < n; j ++ ) //每轮i共选n位(二进制数), 取i的二进制每位判断选或不选 if (i >> j & 1) //i的二进制的第j - 1位是否为1 i & 1 个位{if (cnt & 1) //连续的0为奇数个{is_valid = false; //方案无效break; }// cnt = 0; y总写了, 但是my发现这肯定不会执行}else cnt ++ ;if (cnt & 1) is_valid = false;st[i] = is_valid; //st默认初始化为false :只有is_valid 为true才会标记为合法转移方案}for (int i = 0; i < 1 << n; i ++ ) //减少时间复杂度【预处理所有二进制方案的state状态】{state[i].clear(); //【多轮输入:注意清空】for (int j = 0; j < 1 << n; j ++ )if ((i & j) == 0 && st[i | j]) //符合转移规则state[i].push_back(j); // j为前一个状态能转移到i }memset(f, 0, sizeof f);f[0][0] = 1;//空集也代表一种方案【不用填:变相填满】for (int i = 1; i <= m; i ++ ) //枚举for (int j = 0; j < 1 << n; j ++ ) //枚举此列所有选与不选状态for (auto k : state[j]) //枚举前一列的二进制排列所有状态【已经预处理状态,累加计算合法状态就行】f[i][j] += f[i - 1][k]; //加上前一个状态i-1的方案数 k = state[j] :表示k转移到jcout << f[m][0] << endl; //能转移到m列,且f[m][0]不会"捅"出来, 才是一个合法方案}return 0;
}
最短Hamilton路径
给定一张 n 个点的带权无向图,点从 0∼n−1 标号,求起点 0 到终点 n−1 的最短 Hamilton 路径。
Hamilton 路径的定义是从 0 到 n−1 不重不漏地经过每个点恰好一次。
输入格式
第一行输入整数 n。
接下来 n 行每行 n 个整数,其中第 i 行第 j 个整数表示点 i 到 j 的距离(记为 a[i,j])。
对于任意的 x,y,z,数据保证 a[x,x]=0,a[x,y]=a[y,x] 并且 a[x,y]+a[y,z]≥a[x,z]。
输出格式
输出一个整数,表示最短 Hamilton 路径的长度。
数据范围
1≤n≤20
0≤a[i,j]≤10710^7107
输入样例:
5
0 2 4 5 1
2 0 6 5 3
4 6 0 8 3
5 5 8 0 5
1 3 3 5 0
输出样例:
18
[最短Hamilton路径] :等效旅行商问题:不重不漏地经过每个点恰好一次(每个点都有过路费)
状态表示:二进制枚举走或不走
f[i][j] : (二进制表示i的走法)经过i, 倒数第二个点为j的集合
起点和终点固定,中间过程考虑:
1.哪些点被用过
2.目前到哪个点
#include <cstring>
#include <iostream>
#include <algorithm>using namespace std;const int N = 20, M = 1 << N;int n;
int w[N][N];//费用 【邻接矩阵】
int f[M][N];//二进制表示的第i选法 到达第j个点的最小费用int main()
{cin >> n; for (int i = 0; i < n; i ++ )for (int j = 0; j < n; j ++ )cin >> w[i][j];//相当于有向图【邻接矩阵】memset(f, 0x3f, sizeof f); //求min, 初始化INFf[1][0] = 0; //起点费用0【边界】 for (int i = 0; i < 1 << n; i ++ ) //枚举所有选法for (int j = 0; j < n; j ++ ) //枚举倒数第二个点if (i >> j & 1) 能走到j才有意义!!!【否则剪枝】for (int k = 0; k < n; k ++ ) //过程枚举:起点0-k的最短距离if(i - (1 << j) >> k & 1) //【最后到j,则从起点走到点k的路径不能经过点j】(取0-k中不经过j的状态:减去第j位的1) f[i][j] = min(f[i][j] , f[i - (1 << j)][k] + w[k][j]); //取0-k中不经过j的状态更新//k视为倒数第二个点 [0 --> k+ k --> j] 费用:f[状态][k] + w[k][j]//'+',-'等算术优先级大于 ">>","<<"等位运算符【不清楚时按逻辑加小括号就行】cout << f[(1 << n) - 1][n - 1]; //最终状态[遍历完所有情况(0~2^n-1)][落到终点]return 0;
}
🌲day7
没有上司的舞会 - 树形DP
给定一张 n 个点的带权无向图,点从 0∼n−1 标号,求起点 0 到终点 n−1 的最短 Hamilton 路径。
Hamilton 路径的定义是从 0 到 n−1 不重不漏地经过每个点恰好一次。
输入格式
第一行输入整数 n。
接下来 n 行每行 n 个整数,其中第 i 行第 j 个整数表示点 i 到 j 的距离(记为 a[i,j])。
对于任意的 x,y,z,数据保证 a[x,x]=0,a[x,y]=a[y,x] 并且 a[x,y]+a[y,z]≥a[x,z]。
输出格式
输出一个整数,表示最短 Hamilton 路径的长度。
数据范围
1≤n≤20
0≤a[i,j]≤10710^7107
输入样例:
5
0 2 4 5 1
2 0 6 5 3
4 6 0 8 3
5 5 8 0 5
1 3 3 5 0
输出样例:
18
经典树形DP - 类似状态机+ 邻接矩阵存边
题意
高兴度:不与直接上司匹配 ==> 若选取匹配的两点没有直接连线,加两点的高兴度
==> 即非父子节点【选取没有边相连的两点,加上点(员工)的高兴度】
集合划分 : 选根 / 不选根
状态:u根(当前) s子节点 转移条件:1表示选, 0表示不选
f[u][0] += max(f[s][1], f[s][1]); **不选根 **: 可以选子节点(选或不选都行)
f[u][1] += f[s][0]; 选根 :不能选子节点
关键词:最大独立集问题(还没学过)
#include <cstring>
#include <iostream>
#include <algorithm>using namespace std;const int N = 6010;int n;
int h[N], e[N], ne[N], idx;
int happy[N]; //高兴度 : 可以简写为w[]
int f[N][2];
bool has_fa[N]; //【判断当前节点是否有父节点!(找根,没有父节点则为根)】 (has_father) flagvoid add(int a, int b)
{e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
//树形DP
void dfs(int u)
{f[u][1] = happy[u]; //选择当前节点u, 加上自身的高兴度 [过程初始化]for (int i = h[u]; ~i; i = ne[i]) //遍历i的出边 i -> e[i] {int j = e[i]; //对应邻接点 dfs(j); //邻接点先全部初始化,加上自身高兴度//状态机f[u][1] += f[j][0]; f[u][0] += max(f[j][0], f[j][1]);}
}int main()
{scanf("%d", &n);for (int i = 1; i <= n; i ++ ) scanf("%d", &happy[i]); //高兴度 - 类比w[]权值memset(h, -1, sizeof h); //头结点初始化!for (int i = 0; i < n - 1; i ++ ) //读入n-1条有向边{int a, b;scanf("%d%d", &a, &b);add(b, a); has_fa[a] = true; //b -> a : a有父节点b(直接上司)}int root = 1;//找根节点【根节点没有父节点】while (has_fa[root]) root ++ ; dfs(root); //根节点为起点 : =>输入起点根root 【状态机dfs-树形DP】 =>返回结果printf("%d\n", max(f[root][0], f[root][1])); //集合划分两种:ans = max(选根的高兴度和, 不选根的高兴度和)return 0;
}
相关文章:
蓝桥杯冲刺 - Lastweek - 你离省一仅剩一步之遥!!!(掌握【DP】冲刺国赛)
文章目录💬前言🎯week3🌲day10-1背包完全背包多重背包多重背包 II分组背包🌲day2数字三角形 - 线性DP1015. 摘花生 - 数字三角形🌲day3最长上升子序列 - 线性DP1017. 怪盗基德的滑翔翼 - LIS1014.登山 - LIS最长公共子…...
C++ map与set的学习
1. 关联式容器在初阶阶段,我们已经接触过STL中的部分容器,比如:vector、list、deque、forward_list(C11)等,这些容器统称为序列式容器,因为其底层为线性序列的数据结构,里面存储的是元素本身。关联式容器也…...
【C语言初阶】函数
文章目录💐专栏导读💐文章导读🌷函数是什么?🌷函数的分类🌺库函数🌺自定义函数🌷函数的参数🌷函数的调用🌷函数的嵌套调用和链式访问🌺嵌套调用&a…...
CentOS 7安装redis6.2.6(包括服务开机自启和开放端口)
CentOS 7安装redis6.2.61. 官网下载redis文件2. 校验安装依赖2.1 安装系统默认版本gcc2.2 升级gcc版本3. 解压编译安装4. 修改配置redis.conf4.2 设置密码4.3 绑定ip(可选)5. 启动redis服务并测试5.2 测试安装是否成功5.3 redis开机自启配置6.开放防火墙…...
基于注解的自动装配~
Autowired:实现自动装配功能的注解 Autowired注解能够标识的位置: 标识在成员变量上,此时不需要设置成员变量的set方法标识在成员变量对应的set方法上标识在为当前成员变量赋值的有参构造上使用注解进行自动装配,只要在其成员变量…...
【深度学习】【分布式训练】Collective通信操作及Pytorch示例
相关博客 【深度学习】【分布式训练】Collective通信操作及Pytorch示例 【自然语言处理】【大模型】大语言模型BLOOM推理工具测试 【自然语言处理】【大模型】GLM-130B:一个开源双语预训练语言模型 【自然语言处理】【大模型】用于大型Transformer的8-bit矩阵乘法介…...
Spring常用注解说明
目录 1.常用注解 2.特别说明 3.xml及注解方式 1.常用注解 (1) SpringBootApplication (2) ControllerRestControllerRequestMappingRequestParamPathVariableGetMappingPostMappingPutMappingDeleteMappingResponseBodyRequestBodyCrossOrigin (3) ConfigurationBeanServ…...
13-C++面向对象(纯虚函数(抽象类)、多继承、多继承-虚函数、菱形继承、虚继承、静态成员)
虚析构函数 存在父类指针指向子类对象的情况,应该将析构函数声明为虚函数(虚析构函数) 纯虚函数 纯虚函数:没有函数体且初始化为0的虚函数,用来定义接口规范 抽象类: 含有纯虚函数的类,不可以实…...
Android DataBinding 自定义View实现数据双向绑定
看不懂的可以先看看单向数据绑定:Android DataBinding数据变化时自动更新界面_皮皮高的博客-CSDN博客 然后再确定已经启动了dataBinding的情况下,按下面的顺序来: 首先创建一个自定义View: import android.content.Context imp…...
网络安全中的渗透测试主要那几个方面
渗透测试中主要有软件测试和渗透测试。 1、测试对象不同 软件测试:主要测试的是程序、数据、文档。 渗透测试:对象主要为网络设备、主机操作系统、数据库系统和应用系统。 2、测试内容不同 软件测试:主要工作内容是验证和确认,发…...
Cursor:GPT-4 驱动的强大代码编辑器
Cursor (https://www.cursor.so/)是 GPT-4 驱动的一款强大代码编辑器,可以辅助程序员进行日常的编码。下面通过一个实际的例子来展示 Cursor 如何帮助你编程。这个例子做的事情是网页抓取。抓取的目标是百度首页上的百度热搜,如下…...
C/C++中for语句循环用法及练习
目录 语法 下面是 for 循环的控制流: 实例 基于范围的for循环(C11) 随堂笔记! C语言训练-计算1~N之间所有奇数之和 题目描述 输入格式 输出格式 样例输入 样例输出 环形方阵 干货直达 for 循环允许您编写一个执行特定次数的循环的重复控制结构。…...
AnimatorOverrideController说明
unity-AnimatorOverrideControllerhttps://docs.unity.cn/cn/current/ScriptReference/AnimatorOverrideController.html 用于控制动画器重写控制器的接口。 动画器重写控制器的用途是重写某个控制器的动画剪辑,从而为给定化身定制动画。 在运行时基于相同的 Anim…...
1.4、第三阶段 MySQL数据库
root数据库技术 一、数据库理论 1 什么是数据库技术 数据库技术主要研究如何组织、存储数据,并如何高效地提取和处理数据。 2 什么是SQL SQL(Structured Query Language)结构化查询语言 SQL是操作数据库的命令集,也是功能齐全的…...
LeetCode:202. 快乐数
🍎道阻且长,行则将至。🍓 🌻算法,不如说它是一种思考方式🍀算法专栏: 👉🏻123 一、🌱202. 快乐数 题目描述:编写一个算法来判断一个数 n 是不是快…...
Android 14 新功能之 HighLights:快速实现文本高亮~
日常开发中可能会遇到给 TextView 的全部或部分文本增加高亮效果的需求,以前可能是通过 Spannable 或者 Html 标签实现。 升级 Android 14 后就不用这么迂回了,因其首次引入直接设置高亮的 API:HighLights。需要留意的是 HighLights API 和 …...
[渗透教程]-004-嗅探工具-Nmap
文章目录 Nmap介绍基本操作进阶操作Nmap介绍 nmap是一个网络扫描和主机检测工具,它可以帮助用户识别网络上的设备和服务。获取主机正在运行哪些服务,nmap支持多种扫描,UDP,TCP connect(),TCP SYN(半开扫描) ftp代理,反向标志,ICMP,FIN,ACK扫描,ftp代理,反向标志,ICMP. 可以用于…...
大数据技术之Hive SQL题库-初级
第一章环境准备1.1 建表语句hive>-- 创建学生表 DROP TABLE IF EXISTS student; create table if not exists student_info(stu_id string COMMENT 学生id,stu_name string COMMENT 学生姓名,birthday string COMMENT 出生日期,sex string COMMENT 性别 ) row format delim…...
常见HTTP状态码汇总
文章目录1xx: 信息2xx: 成功3xx: 重定向4xx: 客户端错误5xx: 服务器错误1xx: 信息 状态码描述100 Continue服务器仅接收到部分请求,但是一旦服务器并没有拒绝该请求,客户端应该继续发送其余的请求。101 Switching Protocols服务器转换协议:服…...
蓝桥杯刷题冲刺 | 倒计时15天
作者:指针不指南吗 专栏:蓝桥杯倒计时冲刺 🐾马上就要蓝桥杯了,最后的这几天尤为重要,不可懈怠哦🐾 文章目录1.年号字串2.裁纸刀3.猜生日1.年号字串 题目 链接: 年号字串 - 蓝桥云课 (lanqiao.c…...
中南大学无人机智能体的全面评估!BEDI:用于评估无人机上具身智能体的综合性基准测试
作者:Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位:中南大学地球科学与信息物理学院论文标题:BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接:https://arxiv.…...
基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容
基于 UniApp + WebSocket实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...
多模态商品数据接口:融合图像、语音与文字的下一代商品详情体验
一、多模态商品数据接口的技术架构 (一)多模态数据融合引擎 跨模态语义对齐 通过Transformer架构实现图像、语音、文字的语义关联。例如,当用户上传一张“蓝色连衣裙”的图片时,接口可自动提取图像中的颜色(RGB值&…...
使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装
以下是基于 vant-ui(适配 Vue2 版本 )实现截图中照片上传预览、删除功能,并封装成可复用组件的完整代码,包含样式和逻辑实现,可直接在 Vue2 项目中使用: 1. 封装的图片上传组件 ImageUploader.vue <te…...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
SiFli 52把Imagie图片,Font字体资源放在指定位置,编译成指定img.bin和font.bin的问题
分区配置 (ptab.json) img 属性介绍: img 属性指定分区存放的 image 名称,指定的 image 名称必须是当前工程生成的 binary 。 如果 binary 有多个文件,则以 proj_name:binary_name 格式指定文件名, proj_name 为工程 名&…...
Go 并发编程基础:通道(Channel)的使用
在 Go 中,Channel 是 Goroutine 之间通信的核心机制。它提供了一个线程安全的通信方式,用于在多个 Goroutine 之间传递数据,从而实现高效的并发编程。 本章将介绍 Channel 的基本概念、用法、缓冲、关闭机制以及 select 的使用。 一、Channel…...
搭建DNS域名解析服务器(正向解析资源文件)
正向解析资源文件 1)准备工作 服务端及客户端都关闭安全软件 [rootlocalhost ~]# systemctl stop firewalld [rootlocalhost ~]# setenforce 0 2)服务端安装软件:bind 1.配置yum源 [rootlocalhost ~]# cat /etc/yum.repos.d/base.repo [Base…...
Xela矩阵三轴触觉传感器的工作原理解析与应用场景
Xela矩阵三轴触觉传感器通过先进技术模拟人类触觉感知,帮助设备实现精确的力测量与位移监测。其核心功能基于磁性三维力测量与空间位移测量,能够捕捉多维触觉信息。该传感器的设计不仅提升了触觉感知的精度,还为机器人、医疗设备和制造业的智…...
【免费数据】2005-2019年我国272个地级市的旅游竞争力多指标数据(33个指标)
旅游业是一个城市的重要产业构成。旅游竞争力是一个城市竞争力的重要构成部分。一个城市的旅游竞争力反映了其在旅游市场竞争中的比较优势。 今日我们分享的是2005-2019年我国272个地级市的旅游竞争力多指标数据!该数据集源自2025年4月发表于《地理学报》的论文成果…...
