Count on Me - Connie Talbot - 单曲 - 网易云音乐
目录
前言
01背包
摘花生
最长上升子序列
地宫取宝
AC DP
AC 记忆化搜索
波动数列
个人认为,打表检查是最重要的一步,可以有效提高正确率
结合动规5部曲,套模板速刷,关于动规5部曲
一,确定 dp[i] 或者 dp[i][j] 中 dp[i] 和 i( dp[i][j] 和 i , j )的具体含义(状态),比如斐波那契数列中,dp[i] 表示第 i 个斐波那契数,i 表示第几个
二,找递推公式(通过最后一步,将问题转化成子问题)
三,如何初始化(你要初始化多少个(哪一行哪一列...),初始化的值是什么,同时避免越界)
四,确定遍历顺序(一维前往后后往前;二维从左上角,右下角,甚至是中间等开始遍历)
五,打印dp数组(cout或者printf的形式debug)-> 观察结果
2. 01背包问题 - AcWing题库
AC 代码1
含详细注释 二维dp
#include
using namespace std;
const int N = 1005;
int main()
{
int n, m, w[N] = {0}, v[N] = {0}, dp[N][N];
cin>>n>>m;
for (int i = 1; i <= n; ++i)
cin>>w[i]>>v[i]; // 读入重量weight和价值value
//
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j) {
dp[i][j] = dp[i - 1][j]; // 不放
if (j >= w[i]) // 可以放
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]);
}
cout<
1015. 摘花生 - AcWing题库
AC 代码
/*
dp[i][j] 走到i, j时最多花生数
dp[i][j] = max(dp[i - 1][j] + a[i][j], dp[i][j - 1] + a[i][j]);
西北角是(1, 1), 不用初始化
左上角遍历到右下角
打表检查
*/
#include
using namespace std;
int main()
{
int t, r, c;
int dp[110][110] = {0}, a[110][110] = {0};
cin>>t;
while (t--) {
cin>>r>>c;
for (int i = 1; i <= r; ++i)
for (int j = 1; j <= c; ++j) {
cin>>a[i][j];
dp[i][j] = max(dp[i - 1][j] + a[i][j], dp[i][j - 1] + a[i][j]);
}
cout<
活动 - AcWing
AC 代码
解释在注释中
/*
dp[i] 第i个数结尾的最大长度
dp[i] = max(dp[j] + 1, dp[i])
每个数自成1个序列,初始化为1
从1开始遍历
打表检查
*/
#include
#include
using namespace std;
int main()
{
long long n, a[1010] = {0}, dp[1010] = {0};
cin>>n;
for (int i = 1; i <= n; ++i) {
cin>>a[i];
dp[i] = 1; // 初始化
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j < i; ++j)
if (a[j] < a[i]) // 满足上升条件
dp[i] = max(dp[i], dp[j] + 1);
}
// 因为是第i个结尾, 遍历一遍找最大值
long long ans = 0;
for (int i = 1; i <= n; ++i)
ans = max(ans, dp[i]);
cout<
1212. 地宫取宝 - AcWing题库
一道结合了摘花生和最长上升子序列的题
4维dp,即dp[][][][]
思路
首先明确一点,题目要求的是方案数,dp[i][j][k][c]时,坐标(i, j)处的方案数,来自于上方
取 / 不取,左方取 / 不取,共4种情况的和
考虑到mod 1e9 + 7,最多2个相加就要取余(防止爆int)
初始化中,为了防止下标 -1, 读入v[][],value时,需要++,那么后续遍历最大值时,最大就不是12,而是13了,要细心
/*
(1)含义
dp[i][j][k][c] 表示走到(i, j)时, 已选了k个数, 且最后一个数(即当前的数, 也是最大的数)是c, 的总方案数
(2)递推式
dp[i][j][k][c] = dp[i - 1][j][k][c] + dp[i][j - 1][k][c]; if判断再 + dp[i - 1][j][k - 1][c'] + dp[i][j - 1][k - 1][c'']
(3)初始化
只需对起点初始化, dp[1][1][1][v[i][j]]选, dp[1][1][0][-1]不选, 下标不为-1所以全部 +1
(4)遍历顺序
左上角到右下角
(5)打表debug
*/
#include
using namespace std;
const int N = 55, mod = 1000000007;
int main()
{
int n, m, k, dp[N][N][13][14] = {0}, v[N][N]= {0};
cin>>n>>m>>k;
// 读入每件物品价值
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j) {
cin >> v[i][j];
v[i][j]++; // dp[][][][c], 保证c == 0时, 表示未选
}
// 初始化起点
dp[1][1][1][v[1][1]] = 1; // 选
dp[1][1][0][0] = 1; //不选
// 5层for
for (int i = 1; i <= n; ++i) // 横坐标
for (int j = 1; j <= m; ++j) { // 纵坐标
if (i == 1 && j == 1) continue;
for (int o = 0; o <= k; ++o) // 当前个数o
for (int t = 0; t <= 13; ++t) { // 当前最大值t, 0 ~ 13
int &var = dp[i][j][o][t];
// 加上左边, 上边, 不取的2个方案数
var = (var + dp[i - 1][j][o][t]) % mod;
var = (var + dp[i][j - 1][o][t]) % mod;
// 先判断, 再加上取得2个方案数
if (o > 0 && v[i][j] == t) { // 要取的话, 当前个数至少为1
// 枚举 c'
for (int ct = 0; ct < t; ++ct) {
var = (var + dp[i - 1][j][o - 1][ct]) % mod; // 取之前已取o - 1, 最大值ct, ct == 0表示未取
var = (var + dp[i][j - 1][o - 1][ct]) % mod;
}
}
}
}
// 遍历当前最大值, 所有方案数相加
int ans = 0;
for (int i = 1; i <= 13; ++i) // 考虑到, 读入时的++, 所以是1~13
ans = (ans + dp[n][m][k][i]) % mod;
cout<
关于为什么能把-1传到dp[][][][]作为下标索引,这点,不是很理解,也许程序会自动识别为无效索引
#include
#include // memset()
using namespace std;
int n, m, k;
const int N = 55, mod = 1000000007;
int dp[N][N][13][14], v[N][N];
int dfs(int x, int y, int step, int w)
{
int &var = dp[x][y][step][w]; // 引用当前状态方案数
if (var >= 0) return var; // 记忆化, 已计算的直接返回, 避免重复计算
if (x > n || y > m || step > k) return 0; // 越界
// 判断边界
if (x == n && y == m) {
if (step == k || (step == k - 1 && v[x][y] > w))
return 1; // 满足一种方案的条件
return 0; // 不满足
}
// 枚举4种情况
var = 0; // 保证每条路径的独立
var = (var + dfs(x, y + 1, step, w)) % mod; // 左方, 不取
var = (var + dfs(x + 1, y, step, w)) % mod; // 下方, 不取
if (v[x][y] > w) {
var = (var + dfs(x + 1, y, step + 1, v[x][y])) % mod; // 下方, 取
var = (var + dfs(x, y + 1, step + 1, v[x][y])) % mod; // 左方, 取
}
return var;
}
int main()
{
cin>>n>>m>>k;
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
cin>>v[i][j];
memset(dp, -1, sizeof dp); // 初始化方案数都为 -1, 表示未计算过
cout<
题目
1214. 波动数列 - AcWing题库
视频
AcWing 1214. 波动数列(蓝桥杯C++ AB组辅导课) - AcWing
题解
波动数列 - onlyblues - 博客园 (cnblogs.com)
代码很短,难点在于dp[][]含义和递推式的分析,本质是组合问题
AC 代码
/*
(1)含义
dp[i][j], 只考虑前 i 项, 当前的和模 n 余 j
(2)递推式
dp[i][j] = ( dp[i - 1][get_mod(j - i*a, n) + dp[i - 1][get_mod(j + i*b, n) ) % mod;
(3)初始化
dp[0][0] = 1, 前0项, 和为0模n余0
*/
#include
using namespace std;
const int N = 1010, mod = 100000007;
int dp[N][N];
int get_mod(int a, int b)
{
return (a % b + b) % b; // 返回正余数
}
int main()
{
int n, s, a, b;
cin>>n>>s>>a>>b;
dp[0][0] = 1;
for (int i = 1; i < n; ++i) // 前 i 项
for (int j = 0; j < n; ++j) // 模n的余数j
dp[i][j] = ( dp[i - 1][get_mod(j - i*a, n)] + dp[i - 1][get_mod(j + i*b, n)] ) % mod;
cout<