目录
一、背包问题
1.01背包
2.完全背包
3.多重背包
3.分组背包
二、线性DP
1.数字三角形
2.最长上升子序列
3.最长公共子序列
4.编辑距离
三、区间DP
石子合并
四、计数类DP
整数划分
五、数位统计DP
计数问题
六、状态压缩DP
1.蒙德里安的梦想
2.最短Hamilton路径
七、树形DP
没有上司的舞会
八、记忆化搜索
滑雪
以下代码块中数据输入均可直接进行状态转移操作,节省空间。
#include
using namespace std;
const int N = 1e3 + 10;
int v[N], w[N]; // 花费、价值
void solve1(){
int dp[N][N]; // 前i个物品中容量为j时的最大价值
int n, m;
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 ++){ // 枚举容积
if(j < v[i]) dp[i][j] = dp[i - 1][j]; // j的容积装不下第i件物品
else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - v[i]] + w[i]); // max(不装, 装)
}
}
cout << dp[n][m]; // 答案状态
}
// 降维 滚动数组优化:dp[i][]都由dp[i-1][]转移而来
void solve2(){
int dp[100000]; // 容量为j时的最大价值
int n, m;
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 = m; j >= v[i]; j --){ // 逆序枚举 防止污染前面状态
dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
}
}
cout << dp[m];
}
signed main(){
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int t = 1;
// cin >> t;
while(t --) solve2();
}
#include
using namespace std;
const int N = 1e3 + 10;
int v[N], w[N]; // 花费、价值
// 基本思路(会T)
void solve1(){
int dp[N][N]; // 前i个物品中容量为j时的最大价值
int n, m;
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 ++){ // 枚举能装几件i物品
dp[i][j] = max(dp[i][j], dp[i - 1][j - k * v[i]] + k * w[i]);
}
cout << dp[n][m]; // 答案状态
}
// 优化
void solve2(){
int dp[N][N]; // 前i个物品中容量为j时的最大价值
int n, m;
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 ++){ // 枚举容积
if(j < v[i]) dp[i][j] = dp[i - 1][j];
else dp[i][j] = max(dp[i - 1][j], dp[i][j - v[i]] + w[i]); // 区别01背包的地方:可拿多次i物品
}
cout << dp[n][m]; // 答案状态
}
// 降维优化
void solve3(){
int dp[100000]; // 容量为j时的最大价值
int n, m;
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 ++){ // 区别01背包为逆序枚举
dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
}
}
cout << dp[m];
}
signed main(){
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int t = 1;
// cin >> t;
while(t --) solve1();
}
#include
using namespace std;
const int N = 1e5 + 10;
int v[N], w[N], c[N]; // 花费、价值、数量
int dp[1010][1010]; // 前i个物品中容量为j时的最大价值
int dp[N]; // 容量为j时的最大价值
// 多包问题基本思路 类比完全背包
void solve1(){
int n, m;
cin >> n >> m;
for(int i = 1; i <= n; i ++) cin >> v[i] >> w[i] >> c[i];
for(int i = 1; i <= n; i ++) // 枚举物品编号
for(int j = 0; j <= m; j ++) // 枚举容积
for(int k = 0; k <= c[i]; k ++){ // 枚举能装几件i物品
if(j >= k * v[i]) dp[i][j] = max(dp[i][j], dp[i - 1][j - k * v[i]] + k * w[i]);
}
cout << dp[n][m]; // 答案状态
}
// 优化(数据太水)
void solve2(){
int n, m;
cin >> n >> m;
// 将一定数量的每种物品分成一个个物品
int id = 1;
for(int vol, weal, num, i = 1; i <= n; i ++){
cin >> vol >> weal >> num;
while(num --){
v[id] = vol;
w[id] = weal;
id ++;
}
}
// 转化为01背包问题
for(int i = 1; i < id; i ++) // 枚举物品组号
for(int j = m; j >= v[i]; j --){ // 枚举容积
dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
}
cout << dp[m]; // 答案状态
}
// 二进制优化
void solve3(){
int n, m;
cin >> n >> m;
// 分组
int id = 1;
for(int vol, weal, num, i = 1; i <= n; i ++){
cin >> vol >> weal >> num;
int k = 1;
while(k <= num){
v[id] = k * vol;
w[id ++] = k * weal;
num -= k;
k <<= 1;
}
// 剩余一组
if(num > 0){
v[id] = num * vol;
w[id ++] = num * weal;
}
}
// 转化为01背包问题
for(int i = 1; i < id; i ++) // 枚举物品组号
for(int j = m; j >= v[i]; j --){ // 枚举容积
dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
}
cout << dp[m]; // 答案状态
}
signed main(){
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int t = 1;
// cin >> t;
while(t --) solve3();
}
#include
using namespace std;
const int N = 1e5 + 10;
int v[N], w[N]; // 花费、价值
int dp[N];
// 分组背包 => 多重01背包
void solve(){
int n, m;
cin >> n >> m;
while(n --){
int t;
cin >> t;
for(int i = 1; i <= t; i ++) cin >> v[i] >> w[i];
// 01背包 每组最多拿一个
for(int j = m; j >= 0; j --)
for(int i = 1; i <= t; i ++){ // 遍历每组的每个物品
if(j >= v[i]) dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
}
}
cout << dp[m];
}
signed main(){
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int t = 1;
// cin >> t;
while(t --) solve();
}
#include
using namespace std;
#define int long long
const int N = 5e2 + 10;
int dp[N][N]; // 走到(i, j)的最大和
int g[N][N];
void solve(){
int n;
cin >> n;
for(int i = 1; i <= n; i ++){
for(int j = 1; j <= i; j ++) cin >> g[i][j];
}
// 倒着走 无需考虑边界问题 因为最优路径的和不变
for(int i = n; i >= 1; i --)
for(int j = i; j >= 1; j --){
// (i,j)状态由矩阵正下(i+1, j)或右下(i+1, j+1)状态更新而来
dp[i][j] = max(dp[i + 1][j], dp[i + 1][j + 1]) + g[i][j];
}
cout << dp[1][1];
}
signed main(){
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int t = 1;
// cin >> t;
while(t --) solve();
}
#include
using namespace std;
#define int long long
const int N = 1e5 + 10;
int dp[N]; // 从第1到第i个数长度为i的数列的严格单调递增的子序列最长长度
int a[N];
int stk[N], tt; // 模拟栈
// 弱数据 DP
void solve1(){
int n;
cin >> n;
for(int i = 1; i <= n; i ++) cin >> a[i];
dp[1] = 1;
int res = 0;
for(int i = 1; i <= n; i ++){ // 枚举从第1到第i个数的数列
dp[i] = 1;
for(int j = 1; j <= i; j ++){
if(a[j] < a[i]) dp[i] = max(dp[i], dp[j] + 1);
}
res = max(res, dp[i]); // 更新答案 防止污染
}
cout << res;
}
// 强数据 模拟、贪心
void solve2(){
int n;
cin >> n;
for(int i = 1; i <= n; i ++) cin >> a[i];
stk[++ tt] = a[1];
for(int i = 2; i <= n; i ++){
if(a[i] > stk[tt]) stk[++ tt] = a[i];
else{ // 替换序列中大于等于a[i]的数
int id = lower_bound(stk + 1, stk + tt + 1, a[i]) - stk;
stk[id] = a[i];
}
}
cout << tt;
}
signed main(){
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int t = 1;
// cin >> t;
while(t --) solve2();
}
#include
using namespace std;
#define int long long
typedef pair PII;
const int INF = 0x3f3f3f3f;
const int N = 1e3 + 10;
int dp[N][N]; // a串中1~i字串 与 b串中1~j字串 的最长公共子序列(LCS)的长度
void solve(){
int n, m;
string a, b;
cin >> n >> m >> a >> b;
a = "#" + a;
b = "#" + b;
for(int i = 1; i <= n; i ++){
for(int j = 1; j <= m; j ++){
if(a[i] == b[j]) dp[i][j] = dp[i - 1][j - 1] + 1;
else dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
// cout << dp[i][j] << ' ';
}
// cout << '\n';
}
cout << dp[n][m];
}
signed main(){
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int t = 1;
// cin >> t;
while(t --) solve();
}
#include
using namespace std;
#define int long long
typedef pair PII;
const int INF = 0x3f3f3f3f;
const int N = 1e3 + 10;
int dp[N][N]; // a串中1~i字串 与 b串中1~j字串 的编辑距离(ED, edit distance)最小值
string s[N];
// 得到 a->b 的最短ED
int get(string a, string b){
int n = a.size(), m = b.size();
a = "#" + a;
b = "#" + b;
// 初始化:求min
for(int i = 1; i <= n; i ++){
for(int j = 1; j <= m; j ++) dp[i][j] = INF;
}
// 初始化:对于空串变成字符串s的最小ED为 s.length()
for(int i = 1; i <= n; i ++) dp[i][0] = i;
for(int i = 1; i <= m; i ++) dp[0][i] = i;
for(int i = 1; i <= n; i ++){
for(int j = 1; j <= m; j ++){
int f = (a[i] != b[j]);
dp[i][j] = min({dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + f});
}
}
// cout << dp[n][m] << ' ';
return dp[n][m];
}
void solve(){
int l, t;
cin >> l >> t;
for(int i = 0; i < l; i ++) cin >> s[i];
while(t --){
string str;
int k, res = 0;
cin >> str >> k;
for(int i = 0; i < l; i ++){
if(get(s[i], str) <= k) res ++;
}
cout << res << '\n';
}
}
signed main(){
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int t = 1;
// cin >> t;
while(t --) solve();
}
#include
using namespace std;
#define int long long
typedef pair PII;
const int INF = 0x3f3f3f3f;
const int N = 1e3 + 10;
int dp[N][N]; // 合并i-j区间的最小代价
int sum[N]; // 前缀和
void solve(){
int n;
cin >> n;
for(int x, i = 1; i <= n; i ++){
cin >> x;
sum[i] = sum[i - 1] + x;
}
for(int i = 1; i <= n; i ++){
for(int j = 1; j <= n; j ++) dp[i][j] = INF; //求min 初始化inf
dp[i][i] = 0; // 合并一个数代价为0
}
for(int len = 2; len <= n; len ++) // 枚举合并区间长度
for(int l = 1, r = l + len - 1; r <= n; l ++, r ++) // 枚举左右端点
for(int k = l; k <= r - 1; k ++){ // 枚举分割点 状态转移
dp[l][r] = min(dp[l][r], dp[l][k] + dp[k + 1][r] + (sum[r] - sum[l - 1]));
}
cout << dp[1][n];
}
signed main(){
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int t = 1;
// cin >> t;
while(t --) solve();
}
#include
using namespace std;
#define int long long
typedef pair PII;
const int INF = 0x3f3f3f3f;
const int mod = 1e9 + 7;
const int N = 1e3 + 10;
int dp[N];
void solve(){
int n;
cin >> n;
dp[0] = 1; // 都不选也为一种方案
// 完全背包变形求方案数
for (int i = 1; i <= n; i ++ ){ // 第i个物品体积为i
for(int j = i; j <= n; j ++){ // 枚举体积 j < i无意义
dp[j] += dp[j - i];
dp[j] %= mod;
}
}
cout << dp[n];
}
signed main(){
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int t = 1;
// cin >> t;
while(t --) solve();
}
#include
using namespace std;
#define int long long
typedef pair PII;
const int INF = 0x3f3f3f3f;
const int N = 2e5 + 10;
// 返回1~n中数字x出现的次数
int cnt(int n, int x){
int res = 0;
for(int i = 1; i <= to_string(n).size(); i ++){
int p = pow(10, i - 1);
int l = n / p / 10, r = n % p; // 第i位数截得左右边的数
int d = n / p % 10; // 第i位数
// 四种情况
if(x) res += l * p;
if(x == 0 && l) res += (l - 1) * p;
if(x || l){
if(d == x) res += r + 1;
if(d > x) res += p;
}
}
return res;
}
void solve(){
int a, b;
while(cin >> a >> b && a && b){
if(a > b) swap(a, b);
for(int i = 0; i <= 9; i ++){
cout << cnt(b, i) - cnt(a - 1, i) << ' ';
}
cout << '\n';
}
}
signed main(){
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int t = 1;
// cin >> t;
while(t --) solve();
}
#include
using namespace std;
#define int long long
typedef pair PII;
const int INF = 0x3f3f3f3f;
const int N = 15;
const int M = 1 << 15; // 状态压缩:二进制表示状态
int f[N][M]; // 第i列第j个的状态 j位为1/0表示本列有/无格子伸出
int st[M]; // 表示摆放状态是否合法
void solve(){
int n, m; // n行m列
while(cin >> n >> m && n && m){
// 只横放判断是否合法 预处理每列不能有奇数个连续的空位
for(int i = 0; i < 1 << n; i ++){
int cnt = 0; // 记连续空位个数
st[i] = true; // 初始化i状态无奇数个连续空位合法
// 遍历i列每个状态
for(int j = 0; j < n; j ++){
if(i >> j & 1){ // 第i列j位为1 标记
if(cnt & 1) st[i] = false;
cnt = 0;
}
else cnt ++; // 继续统计
}
if(cnt & 1) st[i] = false; //存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 ++) // 枚举i列状态j
for(int k = 0; k < 1 << n; k ++){ // 枚举i-1列状态k
// 不冲突
if((j & k) == 0 && st[j | k])
f[i][j] += f[i - 1][k];
}
cout << f[m][00000000000] << '\n';
// 排列0 ~ m-1的格子 答案状态为第m列无格子伸出:f[m][0]
}
}
signed main(){
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int t = 1;
// cin >> t;
while(t --) solve();
}
#include
using namespace std;
#define int long long
typedef pair PII;
const int INF = 0x3f3f3f3f;
const int N = 2e5 + 10;
int g[20][20]; // 邻接矩阵存图
int f[1 << 20][20]; // i存走到j的最短路径状态 如走过1、3:i为10100 表示走到j的最短路
void solve(){
int n;
cin >> n; // 0 ~ n - 1
for(int i = 0; i < n; i ++)
for(int j = 0; j < n; j ++) cin >> g[i][j];
// 求最短路 初始化inf
memset(f, 0x3f, sizeof f);
f[00001][0] = 0; // 0点到本身的距离为0
for(int st = 00001; st < 1 << n; st ++){ // 枚举所有点的所有状态
if(! st & 1) continue; // 状态必须包括起点0
for(int j = 0; j < n; j ++){ // 枚举状态终点
if(st >> j & 1) // 状态必须包括起点0
for(int k = 0; k < n; k ++) // 枚举到达j的k点
if((st ^ 1 << j) >> k & 1)
f[st][j] = min(f[st][j], f[st ^ (1 << j)][k] + g[k][j]);
}
}
cout << f[(1 << n) - 1][n - 1]; // f[11111][n - 1]
}
signed main(){
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int t = 1;
// cin >> t;
while(t --) solve();
}
#include
using namespace std;
#define int long long
typedef pair PII;
const int INF = 0x3f3f3f3f;
const int N = 1e4 + 10;
int n, m;
int h[N], e[N], w[N], ne[N], idx; // 邻接表建树
bool st[N];
int f[N][2]; // dp[i][0/1]当前节点选或不选的最大快乐值
void add(int a, int b){
e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
void dfs(int u){
f[u][0] = 0;
f[u][1] = w[u];
for(int i = h[u]; i != -1; i = ne[i]){
int j = e[i];
dfs(j);
// u为父 j为子
f[u][0] += max(f[j][0], f[j][1]); // 不选u的方案为所有j选或不选的方案的最大值的和
f[u][1] += f[j][0]; // 选u则所有j都不能选
}
}
void solve(){
cin >> n;
for(int i = 1; i <= n; i ++){
cin >> w[i];
h[i] = -1;
}
for(int a, b, i = 1; i < n; i ++){
cin >> a >> b;
add(b, a); // b为根节点
st[a] = true; // 标记a有根节点
}
int root = 1;
while(st[root]) root ++;
dfs(root);
cout << max(f[root][0], f[root][1]);
}
signed main(){
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int t = 1;
// cin >> t;
while(t --) solve();
}
#include
using namespace std;
#define int long long
typedef pair PII;
const int INF = 0x3f3f3f3f;
const int N = 3e2 + 10;
int n, m;
int g[N][N];
int f[N][N]; // 以(i, j)为起点的最长路
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1}; // 方向向量
int dp(int x, int y){
int &t = f[x][y]; // 取址赋值
if(t != - 1) return t; // 已更新
t = 1; // 初始化 走过起点为1
for(int i = 0; i < 4; i ++){
int xx = x + dx[i], yy = y + dy[i]; // 下一步位置
if(xx >= 1 && xx <= n && yy >= 1 && yy <= m && g[xx][yy] < g[x][y])
t = max(t, dp(xx, yy) + 1); // 位置合法 更新状态
}
return t;
}
void solve(){
cin >> n >> m;
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= m; j ++) cin >> g[i][j];
memset(f, -1, sizeof f); // 未更新则为-1
int res = 0;
for(int i = 1; i <= n; i ++) // 枚举所有起点
for(int j = 1; j <= m; j ++)
res = max(res, dp(i, j)); // 递归实现
cout << res;
}
signed main(){
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int t = 1;
// cin >> t;
while(t --) solve();
}