赞
踩
DP:
【1】状态表示:需要几个维度来表示状态
(1)集合(所有选法,带有约束条件)
(2)属性(最大值、最小值、数量)
【2】状态计算:如何算出每一步的状态,对应集合的划分(不重+不漏)
**DP的优化:**对代码/计算方程做变形
https://www.cnblogs.com/littlehb/p/15366309.html
01背包:每件物品最多只用一次
条件:
【1】只从前i个物品当中选
【2】总体积小于等于j
状态计算方式:含i的情况+不含i的情况
f
(
i
,
j
)
=
m
a
x
(
f
(
i
−
1
,
j
)
,
f
(
i
−
1
,
j
−
v
i
)
+
w
j
)
f(i,j)=max(f(i-1,j),f(i-1,j-v_i)+w_j)
f(i,j)=max(f(i−1,j),f(i−1,j−vi)+wj)
# include <iostream>
# include <string.h>
# include <algorithm>
# include <stdio.h>
# include <vector>
# include <queue>
# include <unordered_set>
using namespace std;
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++){
cin>>v[i]>>w[i];
}
for(int i=1;i<=n;i++){
for(int j=0;j<=m;j++){
f[i][j] = f[i-1][j];
if(j >= v[i]){
f[i][j] = max(f[i][j], f[i-1][j-v[i]] + w[i]);
}
}
}
cout<<f[n][m]<<endl;
return 0;
}
/*
4 5
1 2
2 4
3 4
4 5
8
*/
dp优化:转化成1维数组
【1】f(i)只用到了f(i-1),因此可用滚动数组
【2】j和j-vi都是小于等于j的,在j的一侧而不是两侧,因此可用1维数组
# include <iostream>
# include <string.h>
# include <algorithm>
# include <stdio.h>
# include <vector>
# include <queue>
# include <unordered_set>
using namespace std;
const int N=1010;
int n,m;
int v[N], w[N];
int f[N]; // 从 f[N][N] 优化而来
int main() {
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>v[i]>>w[i];
}
for(int i=1;i<=n;i++){
// 需要从 m 遍历到 v【i】
//
for(int j=m; j >= v[i];j--){
// f[i][j] = f[i-1][j];
// 优化之后是f[j] = f[j]; 直接省略
f[j] = max(f[j], f[j-v[i]]+w[i]);
}
}
cout<<f[m]<<endl;
return 0;
}
/*
4 5
1 2
2 4
3 4
4 5
8
*/
完全背包:每件物品可用无限次
状态计算方式:
f
(
i
,
j
)
=
m
a
x
(
f
(
i
−
1
,
j
)
,
f
(
i
−
1
,
j
−
v
i
∗
k
)
+
w
j
∗
k
)
f(i,j)=max(f(i-1,j),f(i-1,j-v_i*k)+w_j*k)
f(i,j)=max(f(i−1,j),f(i−1,j−vi∗k)+wj∗k)
k ∗ v i < = j k*v_i<=j k∗vi<=j
# include <iostream>
# include <string.h>
# include <algorithm>
# include <stdio.h>
# include <vector>
# include <queue>
# include <unordered_set>
using namespace std;
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++){
cin>>v[i]>>w[i];
}
for(int i=1;i<=n;i++){
for(int j=0;j<=m;j++){
for(int k=0;k*v[i]<=j;k++){
f[i][j] = max(f[i][j], f[i-1][j-v[i]*k] + w[i]*k);
}
}
}
cout<<f[n][m]<<endl;
return 0;
}
/*
4 5
1 2
2 4
3 4
4 5
10
*/
dp优化:转化成2维数组
【1】将f(i,j)的k次max操作展开
f
[
i
,
j
]
=
m
a
x
(
f
[
i
−
1
,
j
]
,
f
[
i
−
1
,
j
−
v
i
]
+
w
i
,
f
[
i
−
1
,
j
−
2
v
i
]
+
2
w
i
,
f
[
i
−
1
,
j
−
3
v
i
]
+
3
w
i
,
.
.
.
)
f[i,j]=max(f[i-1,j],f[i-1,j-v_i]+w_i,f[i-1,j-2v_i]+2w_i,f[i-1,j-3v_i]+3w_i,...)
f[i,j]=max(f[i−1,j],f[i−1,j−vi]+wi,f[i−1,j−2vi]+2wi,f[i−1,j−3vi]+3wi,...)
【2】f(i,j-v)的k次max操作展开
f
[
i
,
j
−
v
]
=
m
a
x
(
f
[
i
−
1
,
j
−
v
i
]
,
f
[
i
−
1
,
j
−
2
v
i
]
+
w
i
,
f
[
i
−
1
,
j
−
3
v
i
]
+
2
w
i
,
.
.
.
)
f[i,j-v]=max(f[i-1,j-v_i],f[i-1,j-2v_i]+w_i,f[i-1,j-3v_i]+2w_i,...)
f[i,j−v]=max(f[i−1,j−vi],f[i−1,j−2vi]+wi,f[i−1,j−3vi]+2wi,...)
【3】分析可得,f(i,j)的状态转移可以变成是f(i-1,j)+w的结果
f
[
i
,
j
]
=
m
a
x
(
f
[
i
−
1
,
j
]
,
f
[
i
]
[
j
−
v
i
]
+
w
i
)
f[i,j]=max(f[i-1,j],f[i][j-v_i]+w_i)
f[i,j]=max(f[i−1,j],f[i][j−vi]+wi)
合并之后可得:
# include <iostream>
# include <string.h>
# include <algorithm>
# include <stdio.h>
# include <vector>
# include <queue>
# include <unordered_set>
using namespace std;
const int N=1010;
int n,m;
int v[N], w[N];
int f[N]; // 优化
int main() {
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>v[i]>>w[i];
}
for(int i=1;i<=n;i++){
for(int j=v[i];j<=m;j++){
f[j] = max(f[j], f[j-v[i]] + w[i]);
}
}
cout<<f[m]<<endl;
return 0;
}
/*
4 5
1 2
2 4
3 4
4 5
10
*/
多重背包:每个物品个数不一样,不是无限,也不是只有一个,有ki的个数限制
状态转移方程:
f
(
i
,
j
)
=
m
a
x
(
f
(
i
−
1
,
j
)
,
f
(
i
−
1
,
j
−
v
i
∗
k
)
+
w
j
∗
k
)
,
k
从
0
到
s
i
f(i,j)=max(f(i-1,j),f(i-1,j-v_i*k)+w_j*k),k从0到s_i
f(i,j)=max(f(i−1,j),f(i−1,j−vi∗k)+wj∗k),k从0到si
# include <iostream>
# include <string.h>
# include <algorithm>
# include <stdio.h>
# include <vector>
# include <queue>
# include <unordered_set>
using namespace std;
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++){
cin>>v[i]>>w[i];
}
for(int i=1;i<=n;i++){
for(int j=0;j<=m;j++){
for(int k=0;k<=s[i] && k*v[i]<=j ;k++){
f[i][j] = max(f[i][j], f[i-1][j-v[i]*k] + w[i]*k);
}
}
}
cout<<f[n][m]<<endl;
return 0;
}
/*
会超出时间限制!
*/
dp优化:打包物体(二进制拆分)
# include <iostream>
# include <string.h>
# include <algorithm>
# include <stdio.h>
# include <vector>
# include <queue>
# include <unordered_set>
using namespace std;
const int N=25000, M=2010;
int n,m;
int v[N], w[N];
int f[N]; // 优化
int main() {
cin>>n>>m;
int cnt=0;
for(int i=1;i<=n;i++){
int a,b,s;
cin>>a>>b>>s;
int k=1;
while(k<=s){
cnt++;
v[cnt]=a*k;
w[cnt]=b*k;
s-=k;
k*=2;
}
if(s>0){
cnt++;
v[cnt]=a*s;
w[cnt]=b*s;
}
}
n = cnt;
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]);
}
}
cout<<f[m]<<endl;
return 0;
}
/*
4 5
1 2 3
2 4 1
3 4 3
4 5 2
10
*/
分组背包:N组,每组里面若干个,每组里面最多使用一个物品,比如水果组选择了苹果,就不能选香蕉
# include <iostream>
# include <string.h>
# include <algorithm>
# include <stdio.h>
# include <vector>
# include <queue>
# include <unordered_set>
using namespace std;
const int N=25000, M=2010;
int n,m;
int v[N][N], w[N][N], s[N];
int f[N]; // 优化
int main() {
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>s[i];
for(int j=0;j<s[i];j++){
cin>>v[i][j]>>w[i][j];
}
}
for(int i=1;i<=n;i++){
for(int j=m;j>=0;j--){
for(int k=0;k<s[i];k++){
if(v[i][k]<=j){
f[j] = max(f[j],f[j-v[i][k]] + w[i][k]);
}
}
}
}
cout<<f[m]<<endl;
return 0;
}
/*
3 5
2
1 2
2 4
1
3 4
1
4 5
8
*/
题目:数字三角形
# include <iostream>
# include <string.h>
# include <algorithm>
# include <stdio.h>
# include <vector>
# include <queue>
# include <unordered_set>
using namespace std;
const int N=510,INF=1e9;
int n;
int a[N][N];
int f[N][N];
int main() {
cin>>n;
for(int i=1;i<=n;i++){
for(int j=1;j<=i;j++){
cin>>a[i][j];
}
}
for(int i=0;i<=n;i++){
for(int j=0;j<=i;j++){
f[i][j] = -INF;
}
}
f[1][1]=a[1][1];
for(int i=2;i<=n;i++){
for(int j=1;j<=i;j++){
f[i][j]=max(f[i-1][j-1] + a[i][j], f[i-1][j] + a[i][j]);
}
}
int res=-INF;
for(int i=1;i<=n;i++){
res=max(res,f[n][i]);
}
cout<<res<<endl;
return 0;
}
/*
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
30
*/
题目:最长上升子序列
# include <iostream>
# include <string.h>
# include <algorithm>
# include <stdio.h>
# include <vector>
# include <queue>
# include <unordered_set>
using namespace std;
const int N=1010;
int n;
int a[N];
int f[N];
int main() {
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=n;i++){
f[i]=1; // 只有a[i]一个数
for(int j=1;j<i;j++){
if(a[j]<a[i]){
f[i]=max(f[i],f[j]+1);
}
}
}
int res=0;
for(int i=1;i<=n;i++){
res=max(res,f[i]);
}
cout<<res<<endl;
return 0;
}
/*
7
3 1 2 1 8 5 6
4
*/
存储状态转移过程
# include <iostream>
# include <string.h>
# include <algorithm>
# include <stdio.h>
# include <vector>
# include <queue>
# include <unordered_set>
using namespace std;
const int N=1010;
int n;
int a[N];
int f[N], g[N];
int main() {
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=n;i++){
f[i]=1; // 只有a[i]一个数
g[i]=0;
for(int j=1;j<i;j++){
if(a[j]<a[i]){
if(f[i] < f[j]+1){
f[i] = f[j]+1;
g[i] = j;
}
}
}
}
int k=1;
for(int i=1;i<=n;i++){
if(f[k] < f[i]){
k=i;
}
}
cout<<f[k]<<endl;
for(int i=0,len=f[k];i<len;i++){
cout<<a[k]<<" ";//输出迭代序列
k=g[k];
}
return 0;
}
/*
7
3 1 2 1 8 5 6
4
*/
题目:最长公共子序列
# include <iostream>
# include <string.h>
# include <algorithm>
# include <stdio.h>
# include <vector>
# include <queue>
# include <unordered_set>
using namespace std;
const int N=1010;
int n, m;
int a[N], b[N];
int f[N][N];
int main() {
cin>>n>>m;
scanf("%s%s",a+1,b+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]);
if (a[i] == b[j]){
f[i][j] = f[i - 1][j - 1] + 1;
}
}
}
cout<<f[n][m];
return 0;
}
/*
4 5
acbd
abedc
3
*/
题目:石子合并
# include <iostream>
# include <string.h>
# include <algorithm>
# include <stdio.h>
# include <vector>
# include <queue>
# include <unordered_set>
using namespace std;
const int N=310;
int n, m;
int s[N];
int f[N][N];
int main() {
cin>>n;
for (int i = 1; i <= n; i++){
cin>>s[i];
}
for (int i = 1; i <= n; i++){
s[i] += s[i-1];
}//前缀和
for(int len=2;len<=n;len++){
for(int i=1;i+len-1<=n;i++){
int l=i;
int r=i+len-1;
f[l][r] = 1e8; //初始化!非常重要
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]);
}
}
}
cout<<f[1][n];
return 0;
}
/*
4
1 3 5 2
22
*/
题目:整数划分
一个正整数 n 可以表示成若干个正整数之和,n=n1+n2+…+nk
现在给定一个正整数 n,请求出 n 共有多少种不同的划分方法
【二维】
把整数n看成一个容量为n的背包,有n种物品,物品的体积分别是1~n。
我们要求的是 恰好 装满背包的方案数(计数),每种物品 可以用无限次,所以可以看成是一个完全背包。
状态表示
f[i,j]
:从前i中选,总和是j的选法,值就是方案数量。
i
个物品选择了0个f[i−1,j]
:在前i
个物品中选择,结果现在第i
个物品选择了0个,就是说这j
个体积都是前i−1
贡献的。i
个物品选择了s个f[i−1,j−s∗i]
初值
求最大值时,当都不选时,价值是 0。
求方案数时,当都不选时,方案数是 1。
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
const int MOD = 1e9 + 7;
int n;
int f[N][N];
int main() {
cin >> n;
for (int i = 1; i <= n; i++) f[i][0] = 1;
for (int i = 1; i <= n; i++) // 枚举每个物品,物品的序号与物品的体积是相等的,都是i
for (int j = 1; j <= n; j++) { // 枚举背包容量j
if (j >= i) // ① 背包容量j>=当前体积i,可以选择当前数字
f[i][j] = (f[i][j - i] + f[i - 1][j]) % MOD;
else
f[i][j] = f[i - 1][j] % MOD; // ② 放弃当前数字
}
cout << f[n][n] << endl;
return 0;
}
【一维】
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
const int MOD = 1e9 + 7;
int f[N];
int n;
int main() {
cin >> n;
f[0] = 1; // 容量为0时,前 i 个物品全不选也是一种方案
for (int i = 1; i <= n; i++)
for (int j = i; j <= n; j++) // 完全背包从小到大
f[j] = (f[j] + f[j - i]) % MOD;
cout << f[n] << endl;
return 0;
}
题目:计数问题
给定两个整数 a 和 b,求 a 和 b 之间的所有数字中 0∼9 的出现次数。
#include <bits/stdc++.h>
using namespace std;
const int N = 32; // 2^{32}足够int用了
int a[N], al; // 数位分离拆开用的数组
int f[N][N]; // 第一维:第几位数;第二维:走到当前数位,已经取得了多少个
int n; // 当前枚举到的是哪个数
/**
u :从高到低,现在是第几位数
lead :是否考虑前导零
st :到当前深度已经出现n的个数
op :是否贴上界
返回值 :从当前数位u出发,在当前lead,st,op的前提下,可以得到多少个符合题意的数字
*/
int dfs(int u, int lead, int st, int op) {
if (!u) return st; // 递归出口,u==0时,所有数位计算完毕,al是从1开始计数的
if (!lead && !op && ~f[u][st]) return f[u][st]; // 非前导0 + 不贴上界 + 算过
// u位上的最大值
int up = op ? a[u] : 9; // 如果贴上界,则到op,否则可以全部取到
int res = 0; // 按上面三个条件lead,st,op走到u这个数位时,最终可以获取到多少个数呢?
for (int i = 0; i <= up; i++) {
int sum = st;
// ① 前面出现过非0数字 或者 本位置非0
// ② 当前数位是要查找的数字
if ((!lead || i > 0) && i == n) sum++;
// 如果原来是贴上界,现在继续贴上界,那么贴上界继续
res += dfs(u - 1, lead && !i, sum, op && i == a[u]);
}
// 记忆化
if (!lead && !op) f[u][st] = res;
return res;
}
int calc(int x) {
al = 0;
while (x) a[++al] = x % 10, x /= 10; // 高位在右,低位在左
// al :从al位开始
// lead :存在前导0
// st :前面填的数中数字n的个数是0个
// op :贴上界
return dfs(al, 1, 0, 1);
}
int main() {
int l, r;
while (cin >> l >> r, l + r) { // l+r用的漂亮,只有两个都是0时,l+r才能是0,等同于 l || r
if (l > r) swap(l, r); // 谁大谁小还不一定,这题真变态
for (n = 0; n <= 9; n++) { // 0,1,2,3,...个数都有多少个
memset(f, -1, sizeof(f)); // 每轮需要初始化dp数组
cout << calc(r) - calc(l - 1) << ' ';
}
cout << endl;
}
return 0;
}
题目:蒙德里安的梦想
先放横的,再放竖的
当前摆完横着的小方块之后,剩余的位置,如果能用竖着的小方块 填满 就合法,否则不合法
如果每一列所有连续空着的小方块个数存在奇数个,必然填充不满(不合法情况)
状态表示
f
[
i
]
[
j
]
:已经摆完前
i
列,且第
i
列的状态为
j
的所有方案
f[i][j]:已经摆完前i列,且第i列的状态为j的所有方案
f[i][j]:已经摆完前i列,且第i列的状态为j的所有方案
f【0】【i】中,除了f【0】【0】都是非法的,所以赋值为0
最终结果
已经摆完第m列,并且第m列的状态为0,也就是第m列没有任何一个小方格新去摆放横条
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 12;
const int M = 1 << N;
int n, m;
LL f[N][M]; // 已经摆完前i列并且第i列的状态为j的所有方案
vector<int> v[M]; // 对于每个状态而言,能转转移到它的状态有哪些,预处理一下(二维数组)
int ok[M]; // 某种状态是否合法,就是是不是存在奇数个连续0
int main() {
while (cin >> n >> m, n + m) {
// ① 预处理:枚举行数n的每个二进制位,可以枚举出每种可能出现的状态对应的二进制表示,这些状态有些是不合法的
// 只有不存在连续奇数个数字0的状态才是合法的,一旦n确定了,这个有效状态是可以预处理出来的
for (int i = 0; i < 1 << n; i++) {
int cnt = 0; // 连续0的个数
ok[i] = 1; // 初始设置此状态是合法的
for (int j = 0; j < n; j++) // 遍历此状态的每一个二进制位,开始检查
if (i >> j & 1) { // 如果本位是1,表示连续0发生中断,需要统计连续0的个数,并且记得清空cnt
if (cnt & 1) { // 奇数个连续0, (cnt & 1) = (cnt % 2 >0)
ok[i] = 0; // 连续0发生中断,此状态为不合法状态
break; // 不用再往后看了,一次失足就不挽救
}
cnt = 0; // 连续个零计数重新开始
} else
cnt++; // 连续0的计数器++
// 最后一个cnt++后,依然可能有连续奇数个,举个栗子:n=4=(0100)_2,完成数位枚举后,cnt=1,也就是高位存在奇数个0
if (cnt & 1) ok[i] = 0;
}
// ② 预处理:枚举每个状态,获取可能是从哪些有效状态转移过来
for (int i = 0; i < 1 << n; i++) {
// 多组数据,每次预处理时清空一下
// vector<int> v[M] 是一个二维数组,初始化比较麻烦,需要用循环遍历第一维,然后再v[i].clear()进行清空
v[i].clear();
// 状态i,从哪些状态转化而来?
for (int j = 0; j < 1 << n; j++) // j为前序状态
// (1) i & j==0 同一行不能同时探出小方格,那样会有重叠
// (2) 解释一下ok[i | j]
// 比如: 01000 | 10101 = 11101,描述当前完成状态叠加后的最终状态,在预处理的数组中找一下,是不是合法状态
if ((i & j) == 0 && ok[i | j]) v[i].push_back(j);
}
// 多组数据,每次清零
memset(f, 0, sizeof f);
// 初始方案数
f[0][0] = 1; // 可以理解为 从虚拟的第0开始(第一个0),还没有向右探出小方格(第二个0),此时的方案数只有1种。
// 如果你想为什么不是0种,下面的递推关系就会让你明白,0做为基底,就啥也递推不出来了。
// DP正式开始
for (int i = 1; i <= m; i++)
for (int j = 0; j < 1 << n; j++) // 遍历第i列的所有状态j
for (auto k : v[j]) // 遍历第i-1列的所有状态k
f[i][j] += f[i - 1][k]; // 每个合法状态,均需从它的前序有效状态转移而来
// 输出答案
cout << f[m][0] << endl;
}
return 0;
}
题目:最短Hamilton路径
#include <bits/stdc++.h>
using namespace std;
const int N = 20; // 好小的上限N,大的没法状态压缩实现,2^N不能太大啊!
const int M = 1 << N; // 2的N次方
int w[N][N]; // 邻接矩阵,记录每两个点之间的距离
int f[M][N]; // DP状态数组,记录每一步的最优解
int n; // n个结点
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);
// ① 初始化,从0出发到0结束,路线状态表示为1
f[1][0] = 0; // 从0走到0,路线为1,也就是二进制表示法为(1)_2,表示0出现过
for (int i = 0; i < (1 << n); i++) // 枚举所有路线
for (int j = 0; j < n; j++) // 枚举每个节点作为阶段性终点
if (i >> j & 1) { // 这个节点是不是包含在路径中
for (int k = 0; k < n; k++) // 引入结点k,使得距离更短
// 需要满足i这个路径中除去j这个点,一定要包含k这个点
if ((i - (1 << j)) >> k & 1)
f[i][j] = min(f[i][j], f[i - (1 << j)][k] + w[k][j]);
}
// 最终经历了所有结点,并且最后停在n-1(最后一个点,因为坐标从0开始)这个点
cout << f[(1 << n) - 1][n - 1] << endl;
return 0;
}
题目:没有上司的舞会
f [ u , 0 ] :所有以 u 为根的子树中选,并且不选 u 这个点的方案 f[u,0]:所有以u为根的子树中选,并且不选u这个点的方案 f[u,0]:所有以u为根的子树中选,并且不选u这个点的方案
f [ u , 1 ] :所有以 u 为根的子树中选,并且选 u 这个点的方案 f[u,1]:所有以u为根的子树中选,并且选u这个点的方案 f[u,1]:所有以u为根的子树中选,并且选u这个点的方案
状态转移
f
[
u
,
0
]
=
∑
m
a
x
(
f
(
s
o
n
,
0
)
,
f
(
s
o
n
,
1
)
)
f[u,0]=∑max(f(son,0),f(son,1))
f[u,0]=∑max(f(son,0),f(son,1))
f [ u , 1 ] = ∑ f ( s o n , 0 ) f[u,1]=∑f(son,0) f[u,1]=∑f(son,0)
#include <bits/stdc++.h>
using namespace std;
const int N = 6010;
int happy[N]; // 快乐值
int in[N]; // 入度
int f[N][2]; // dp的状态结果数组
int n;
// 构建邻接表
int h[N], e[N], ne[N], idx;
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
// 通过深度优先搜索,对树进行遍历
void dfs(int u) {
for (int i = h[u]; ~i; i = ne[i]) {
int v = e[i];
dfs(v); // 继续探索它的孩子,它的值是由它的孩子来决定的
f[u][1] += f[v][0]; // 它选择了,它的孩子就不能再选
f[u][0] += max(f[v][0], f[v][1]); // 它不选择,那么它的每一个孩子,都是可以选择或者不选择的
}
// 不管是不是叶子结点,都会产生happy[u]的价值
f[u][1] += happy[u];
}
int main() {
// 邻接表表头初始化
memset(h, -1, sizeof h);
cin >> n;
for (int i = 1; i <= n; i++) cin >> happy[i];
// 读入树
for (int i = 1; i < n; i++) { // n-1条边
int x, y;
cin >> x >> y;
add(y, x);
in[x]++; // 记录入度,因为需要找出大boss
}
// 从1开始找根结点
int root = 1;
while (in[root]) root++; // 找到根结点,入度为0
// 递归
dfs(root);
// 取两个
printf("%d\n", max(f[root][0], f[root][1]));
return 0;
}
递归求解DP问题——记忆化搜索
题目:滑雪
分4个方向讨论:上下左右
#include <bits/stdc++.h>
using namespace std;
const int N = 310;
int dx[] = {-1, 0, 1, 0};
int dy[] = {0, 1, 0, -1};
int g[N][N]; // 邻接矩阵,地图
int f[N][N]; // 记录从i到j的最大距离
int n, m; // 行与列
int res; // 结果
// 记忆化搜索
int dfs(int x, int y) {
if (~f[x][y]) return f[x][y];
// 求最长的轨迹,最起码是1
f[x][y] = 1;
for (int i = 0; i < 4; i++) {
int tx = x + dx[i], ty = y + dy[i];
if (tx < 1 || tx > n || ty < 1 || ty > m) continue;
if (g[x][y] > g[tx][ty])
f[x][y] = max(f[x][y], dfs(tx, ty) + 1);
}
return f[x][y];
}
int main() {
cin >> n >> m; // n行 m列
// 读入每一个点的高度
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
cin >> g[i][j];
// 初始化-1
memset(f, -1, sizeof f);
// 从每一个点出发
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
res = max(res, dfs(i, j));
// 输出结果
printf("%d\n", res);
return 0;
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。