1. 什么是动态规划
-
英语: Dynamic programming ,简称DP。因此刷算法的人,一般称 动态规划问题 为 DP问题 。
-
核心概念:保存 递归时 的结果, 以减少后续解决同样的问题时所花费的时间。
-
本质:利用已解决问题的答案去解决新问题 。 即用小问题的解法去解大问题。
-
使用场景:1. 最优子结构性质;2. 无后效性;3. 子问题重叠性质
2. 闫氏dp分析法
有限集合中的最值
DP问题两个阶段:
- 状态表示
- 集合:对于集合的定义(核心)
- 属性:最大值、最小值、数量
- 状态计算
- 划分子集
划分子集依据:寻找最后一个不同点
3. 背包问题
/*
1. 01背包: f[i][j] = max(f[i - 1][j], f[i - 1][j - v] + w);
2. 完全背包: f[i][j] = max(f[i - 1][j], f[i][j - v] + w);
*/
#include<iostream>
using namespace std;
const int N = 1010;
int n, m;
int v[N], W[N];//v: 体积,w: 价值
//int f[N][N];//只选i件物品不超过j体积的总价值
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 = 1; j <= m; j++ ) {
for(int j = v[i]; j<= m; j++ ){
//f[i][j] = f[i - 1][j];
//f[j] = f[j];
//等价于上面式子
//if (j >= v[i])
//f[i][j] = max(f[i-1][j], f[i][j-v[i]]) + w[i];
f[j] = max(f[j], f[j-v[i]]) + w[i];
}
//cout << f[n][m] << endl;
cout << f[m] << endl;
return 0;
}
4. 石子合并(区间DP问题)
题目描述
在一个圆形操场的四周摆放 N 堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。
试设计出一个算法,计算出将 N 堆石子合并成 1 堆的最小得分和最大得分。
输入格式
数据的第 1 行是正整数 N,表示有 N 堆石子。
第 2 行有 N 个整数,第 i 个整数 ai表示第i堆石子的个数。
输出格式
输出共 2 行,第 1 行为最小得分,第 2 行为最大得分。
输入输出样例
输入 #1
4
4 5 9 4
输出 #1
43
54
/*
第一步状态表示:
确定集合与属性,f[i][j]表示将第i堆石子到第j堆石子合并成一堆的方案的集合,属性为最小值
第二步状态计算:
f[i][j]可以分为f[i][k]和f[k+1][j]合并,k的范围从i到j
故f[i][j] = f[i][k] + f[k+1][j] + s[j] - s[i-1]
(其中s为前n项合,用s[j] - s[i-1]表示从i到j的部分合)
*/
#include<iostream>
using namespace std;
int N, a[400], s[400], dp[400][400];//放最小
int f[110][110];//放最大
int main(){
cin >> N;
for(int i=1;i<=N;i++){
cin >> a[i];
s[i] = s[i-1] + a[i];
//dp[i][i] = a[i];
}
for(int len=2;len<=N;len++){
for(int i=1;i<=N-len+1;i++){
int j = i + len - 1;
dp[i][j] = 1e8;
for(int k=i;k<j;k++){
f[i][j] = max(f[i][j], f[i][k] + f[k+1][j] + s[j] - s[i-1]);
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k+1][j] + s[j] - s[i-1]);
}
}
}
cout << dp[1][N] << endl;//最小值
cout << f[1][N] << endl; //最大值
return 0;
}
5. 传球游戏
【问题描述】
上体育课的时候,小蛮的老师经常带着同学们一起做游戏。这次,老师带着同学们一起做传球游戏。
游戏规则是这样的:n个同学站成一个圆圈,其中的一个同学手里拿着一个球,当老师吹哨子时开始传球,每个同学可以把球传给自己左右的两个同学中的一个(左右任意),当老师再次吹哨子时,传球停止,此时,拿着球没传出去的那个同学就是败者,要给大家表演一个节目。
聪明的小蛮提出一个有趣的问题:有多少种不同的传球方法可以使得从小蛮手里开始传的球,传了m次以后,又回到小蛮手里。两种传球的方法被视作不同的方法,当且仅当这两种方法中,接到球的同学按接球顺序组成的序列是不同的。比如有3个同学1号、2号、3号,并假设小蛮为1号,球传了3次回到小蛮手里的方式有1->2->3->1和1->3->2->1,共2种。
输入格式
共一行,有两个用空格隔开的整数n,m(3<=n<=30,1<=m<=30)。
输出格式
t共一行,有一个整数,表示符合题意的方法数。
样例输入
3 3
样例输出
2
数据规模和约定
40%的数据满足:3<=n<=30,1<=m<=20
100%的数据满足:3<=n<=30,1<=m<=30
//思路:根据题目“球传了3次回到小蛮手里的方式有”
//可以将集合定义为dp[i][j],球传了i次回到j手上的方法总数
//dp[i][j] = dp[i-1][l] + dp[i-1][r];
//AC代码
#include<iostream>
using namespace std;
const int N = 40;
int n, m, dp[N][N];//传i次回到j手上的方法
int main(){
cin >> n >> m ;
dp[0][1] = 1;
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
int l = j - 1, r = j + 1;
if(j == 1) l = n;
if(j == n) r = 1;
dp[i][j] = dp[i-1][l] + dp[i-1][r];
}
}
cout << dp[m][1] << endl;
return 0;
}