插入dp学习笔记
定义
插入
插入
- 乱搞型:状态定义天马行空,但始终围绕着将新元素插入到旧元素已有集合中
- 套路型:
表示前 个数,现在构成 个连续段的方案数 最优解,此外根据实际情况添加状态,转移则是用新元素新建连续段 合并两个连续段 扩张连续段左侧或右侧
乱搞型
模板
说是模板,其实这种类型谈不上什么模板,每一题的状态定义几乎都不一样,都有奇奇怪怪的某一维,所以此题也可以视为经典题。
CF466D
定义
对于转移,我们有需要满足推平下一个数的条件,以及同一位置不能同时开启
/*
address:https://codeforces.com/problemset/problem/466/D
AC 2024/12/24 20:45
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int mod = 1e9 + 7;
const int N = 2005;
inline void trans(int& x, int y) { x = (x + y) % mod; }
int dp[N][N];
int n, h;
int a[N];
int main() {
scanf("%d%d", &n, &h);
for (int i = 1;i <= n;i++) scanf("%d", &a[i]), a[i] = h - a[i];
dp[0][0] = 1;
for (int i = 0;i < n;i++)
for (int j = 0;j <= i;j++) {
int x = a[i + 1];
if (j + 1 == x) {
trans(dp[i + 1][j + 1], dp[i][j]); //开启一个新区见
trans(dp[i + 1][j], LL(dp[i][j]) * j % mod); //关闭又开启一个新区间
trans(dp[i + 1][j], dp[i][j]); //开启一个区间后马上关闭该区间
}
if (j == x) {
if (j > 0) trans(dp[i + 1][j - 1], LL(dp[i][j]) * j % mod); //关闭一个区间
trans(dp[i + 1][j], dp[i][j]); //什么事都不干
}
}
printf("%d", dp[n][0]); //最后所有区间都关闭了
return 0;
}
实例
AT_dp_t
考虑插入一个数时要考虑插入的数与最后一个数的相对大小关系,并不在意绝对数值,同时方案数计算依赖于又多少数满足条件,所以定义
/*
address:https://atcoder.jp/contests/dp/tasks/dp_t
AC 2024/12/24 21:11
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 3005;
const int mod = 1e9 + 7;
char s[N];
int n;
LL dp[N][N], tmp[N];
inline void trans(LL& x, LL y) { x = (x + y + mod) % mod; }
int main() {
scanf("%d%s", &n, s + 1);
for (int i = 0;i < n;i++) dp[1][i] = 1;
for (int i = 1;i <= n;i++) {
fill(tmp, tmp + n + 1, 0);
for (int j = 0;j <= n;j++)
if (s[i] == '>') trans(tmp[0], dp[i][j]), trans(tmp[j], -dp[i][j]);
else trans(tmp[j], dp[i][j]), trans(tmp[n - i], -dp[i][j]);
for (int j = 1;j <= n;j++) trans(tmp[j], tmp[j - 1]);
for (int j = 0;j <= n;j++) dp[i + 1][j] = tmp[j];
}
LL ans = 0;
for (int i = 0;i < n;i++) trans(ans, dp[n][i]);
printf("%lld", ans);
return 0;
}
拓展练习
CF626F 可以排序,定义
Code
套路型
先来思考一个问题,怎么用插入dp求
最终答案即为
听起来很奇怪,但这是这一类插入
实例
Seatfriends
先不管相隔的距离,以套路插入
Phoenix and Computers
按套路,定义
考虑转移,对于新建一个连续段,我们可以在
对于扩展连续段,我们可以直接在左右两侧添加,也可以隔一个位置加,同时把隔的那个位置自动打开,故有以下转移
对于合并连续段,由于两个连续段间若距离为
固定区间间距离为
这时就有朋友要问了:“作者作者,你为什么在扩展区间和新建区间不考虑区间间的距离?现在又凭什么能固定距离为
观察一下我们的定义,是没有考虑绝对距离的,只有考虑相对间距,对于两个区间间的距离,我们也就可以随意调整,同时转移式的正确与齐全也带来了一种“自适应性”,能保证不合法的状态一定不会转移到最终状态,例如当
参考代码:
/*
address:https://codeforces.com/problemset/problem/1515/E
AC 2024/12/28 10:41
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 405;
int mod;
int n;
LL dp[N][N];
inline void trans(LL& x, LL y) { x = (x + y) % mod; }
int main() {
scanf("%d%d", &n, &mod);
dp[0][0] = 1;
for (int i = 0;i < n;i++)
for (int j = 0;j <= i;j++) {
trans(dp[i + 1][j + 1], dp[i][j] * (j + 1));
trans(dp[i + 1][j], dp[i][j] * j << 1);
trans(dp[i + 2][j], dp[i][j] * j << 1);
if (j > 1) {
trans(dp[i + 2][j - 1], dp[i][j] * (j - 1 << 1));
trans(dp[i + 3][j - 1], dp[i][j] * (j - 1));
}
}
printf("%lld", dp[n][1]);
return 0;
}
Ant Man
先观察一下题目,发现对于题目中的计算公式可以费用提前计算但前提是在当前插入数的对应方向会继续插入数,可题目已经定义了起点和终点,所以除了起点和终点外,其他座椅的左右两侧都一定会继续插入椅子,故放心大胆使用费用提前计算。
定义
考虑转移:
段新增:若插入的数不为
同时在添加非
段扩张,以同样思路进行分析,考虑规避不合法情况,得到以下转移式:
段合并:
代码:
/*
address:https://codeforces.com/problemset/problem/704/B
AC 2025/1/3 20:59
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 5005;
int n, s, e;
int x[N], a[N], b[N], c[N], d[N];
LL dp[N][N];
inline void trans(LL& x, LL y) { x = x < y ? x : y; }
int main() {
scanf("%d%d%d", &n, &s, &e);
for (int i = 1;i <= n;i++) scanf("%d", &x[i]);
for (int i = 1;i <= n;i++) scanf("%d", &a[i]);
for (int i = 1;i <= n;i++) scanf("%d", &b[i]);
for (int i = 1;i <= n;i++) scanf("%d", &c[i]);
for (int i = 1;i <= n;i++) scanf("%d", &d[i]);
for (int i = 0;i <= n;i++)
for (int j = 0;j <= n;j++) dp[i][j] = 1e18;
dp[0][0] = 0;
for (int i = 0;i < n;i++)
for (int j = 0;j <= i;j++)
if (i + 1 == s) {
trans(dp[i + 1][j + 1], dp[i][j] - x[i + 1] + d[i + 1]);
if (j > 0) trans(dp[i + 1][j], dp[i][j] + x[i + 1] + c[i + 1]);
}
else if (i + 1 == e) {
trans(dp[i + 1][j + 1], dp[i][j] - x[i + 1] + b[i + 1]);
if (j > 0) trans(dp[i + 1][j], dp[i][j] + x[i + 1] + a[i + 1]);
}
else {
if (i + 1 <= s || i + 1 <= e || j + 1 > 2) trans(dp[i + 1][j + 1], dp[i][j] - (x[i + 1] << 1) + d[i + 1] + b[i + 1]);
if (j > 0) {
if (j > 1 || i + 1 < s) trans(dp[i + 1][j], dp[i][j] + b[i + 1] + c[i + 1]);
if (j > 1 || i + 1 < e) trans(dp[i + 1][j], dp[i][j] + a[i + 1] + d[i + 1]);
}
if (j > 1) trans(dp[i + 1][j - 1], dp[i][j] + (x[i + 1] << 1) + a[i + 1] + c[i + 1]);
}
printf("%lld", dp[n][1]);
return 0;
}
Boss
最后给大家隆重介绍插入
大致说一下,先排序,定义
/*
address:http://vjudge.net/problem/DMOJ-utso21p7
AC 2025/1/10 22:06
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 505;
const int mod = 1e9 + 7;
int n;
int a[N], f[N];
int dp[2][N][N][3];
inline void trans(int& x, const int y, const int z) { x = (x + 1ll * y * z % mod) % mod; }
int main() {
scanf("%d", &n);
for (int i = 1;i <= n;i++) scanf("%d", &a[i]);
sort(a + 1, a + n + 1);
for (int i = 1;i <= n;i++)
for (int j = 31;j >= 0;j--)
if (a[i] >> j & 1) {
f[i] = j;
break;
}
dp[1][0][1][0] = 1;
for (int i = 1;i < n;i++) {
for (int j = 0;j <= i + 1;j++)
for (int k = 0;k + j <= i + 1;k++)
for (int l = 0;l < 3;l++) dp[i + 1 & 1][j][k][l] = 0;
for (int j = 0;j <= i;j++)
for (int k = 0;k + j <= i;k++) {
const int c0 = dp[i & 1][j][k][0], c1 = dp[i & 1][j][k][1], c2 = dp[i & 1][j][k][2];
const int l = (i & 1) ^ 1;
if (f[i + 1] - f[i] == 0) {
// new
trans(dp[l][j][k + 1][0], c0, j + k + 1);
trans(dp[l][j][k + 1][1], c1, j + k);
trans(dp[l][j][k + 1][0], c1, 1);
trans(dp[l][j][k + 1][2], c2, j + k + 1);
// extend
trans(dp[l][j][k][0], c0, j + k);
trans(dp[l][j][k][1], c1, j + k);
trans(dp[l][j][k][2], c2, j + k + 1);
if (j > 0) {
trans(dp[l][j - 1][k + 1][0], c0, j);
trans(dp[l][j - 1][k + 1][1], c1, j - 1);
trans(dp[l][j - 1][k + 1][0], c1, 1);
trans(dp[l][j - 1][k + 1][2], c2, j);
}
// merge
if (j > 0) {
trans(dp[l][j - 1][k][0], c0, j);
trans(dp[l][j - 1][k][1], c1, j - 1);
trans(dp[l][j - 1][k][2], c2, j);
}
}
if (f[i + 1] - f[i] == 1) {
// new
if (j == 0) trans(dp[l][k][1][0], c0, 1);
if (j == 0) trans(dp[l][k][1][1], c0, k);
if (j == 1) trans(dp[l][k][1][2], c1, k + 1);
if (j == 0) trans(dp[l][k][1][2], c2, k + 1);
// extend
if (j == 0) trans(dp[l][k][0][1], c0, k);
if (j == 0 && k > 0) trans(dp[l][k - 1][1][1], c0, k - 1);
if (j == 0 && k > 0) trans(dp[l][k - 1][1][0], c0, 1);
if (j == 1) trans(dp[l][k][0][2], c1, k + 1);
if (j == 1 && k > 0) trans(dp[l][k - 1][1][2], c1, k);
if (j == 0) trans(dp[l][k][0][2], c2, k + 1);
if (j == 0 && k > 0) trans(dp[l][k - 1][1][2], c2, k);
// merge
if (k > 0) {
if (j == 0) trans(dp[l][k - 1][0][1], c0, k - 1);
if (j == 1) trans(dp[l][k - 1][0][2], c1, k);
if (j == 0) trans(dp[l][k - 1][0][2], c2, k);
}
}
}
const int j = i & 1, k = j ^ 1;
if (f[i + 1] - f[i] >= 2) {
// new
trans(dp[k][0][1][2], dp[j][1][0][1], 1);
trans(dp[k][0][1][2], dp[j][0][1][0], 1);
trans(dp[k][0][1][2], dp[j][0][0][2], 1);
// extend
trans(dp[k][0][0][2], dp[j][1][0][1], 1);
trans(dp[k][0][0][2], dp[j][0][1][0], 1);
trans(dp[k][0][0][2], dp[j][0][0][2], 1);
}
}
printf("%d\n", ((dp[n & 1][1][0][1] + dp[n & 1][0][1][0]) % mod + dp[n & 1][0][0][2]) % mod);
return 0;
}
总结
插入
宝剑锋从磨砺出,梅花香自苦寒来
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理