一、数列问题
二、背包问题
1、01背包
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e3 + 10;
int v[maxn], c[maxn];
int dp[maxn];//背包容量为i的最大价值
int main() {
int cmax, n;
cin >> cmax >> n;
for(int i = 1; i <= n; i++) cin >> c[i];
for(int i = 1; i <= n; i++) cin >> v[i];
for(int i = 1; i <= n; i++) {
for(int j = cmax; j >= c[i]; j--) {
dp[j] = max(dp[j], dp[j - c[i]] + v[i]);
}
}
cout << dp[cmax] << endl;
return 0;
}
2、完全背包
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e3 + 10;
int c[maxn], v[maxn];
int dp[maxn];
int main() {
int cmax, n;
cin >> cmax >> n;
for(int i = 1; i <= n; i++) cin >> c[i];
for(int i = 1; i <= n; i++) cin >> v[i];
for(int i = 1; i <= n; i++) {
for(int j = c[i]; j <= cmax; j++) {
for(int k = 0; k * c[i] <= j; k++) {
dp[j] = max(dp[j], dp[j - k * c[i]] + k * v[i]);
}
}
}
cout << dp[cmax];
return 0;
}
/*
10 4
2 3 4 7
1 3 5 9
*/
~优化2维
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e3 + 10;
int c[maxn], v[maxn], dp[maxn];
int main() {
int cmax, n;
cin >> cmax >> n;
for(int i = 1; i <= n; i++) cin >> c[i];
for(int i = 1; i <= n; i++) cin >> v[i];
for(int i = 1; i <= n; i++) {
for(int j = c[i]; j <= cmax; j++) {
dp[j] = max(dp[j], dp[j - c[i]] + v[i]);
}
}
cout << dp[cmax];
return 0;
}
3、多重背包
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e3 + 10;
int c[maxn], v[maxn], num[maxn];
int dp[maxn];
int main() {
int cmax, n;
cin >> cmax >> n;
for(int i = 1; i <= n; i++) cin >> c[i];
for(int i = 1; i <= n; i++) cin >> v[i];
for(int i = 1; i <= n; i++) cin >> num[i];
for(int i = 1; i <= n; i++) {
for(int j = cmax; j >= c[i]; j--) {
for(int k = 0; k * num[i] <= j; k++) {
dp[j] = max(dp[j], dp[j - k * c[i]] + k * v[i]);
}
}
}
for(int i = 1; i <= cmax; i++) cout << dp[i] << " ";
cout << endl;
cout << dp[cmax];
return 0;
}
/*
20 4
9 9 4 1
3 5 9 8
3 1 2 3
*/
三、区间类动态规划
特点:每次处理一个区间,并将剩下的区间合并起来
1、P1880 石子合并
#include <bits/stdc++.h>
using namespace std;
const int inf = 1e6 + 10;
const int maxn = 205;
int a[maxn];
int dp1[maxn][maxn], dp2[maxn][maxn];//分别表示最小得分和最大得分
int sum[maxn];
int main() {
int n; cin >> n;
for(int i = 1; i <= n; i++) cin >> a[i];
for(int i = 1; i <= n; i++) {
a[i + n] = a[i];//处理环
}
for(int i = 1; i <= n * 2; i++) {
sum[i] = sum[i - 1] + a[i];
}
for(int l = 2; l <= n; l++) {//枚举合并的堆数
for(int i = 1; i <= 2 * n - l + 1; i++) {//枚举起点
int j = i + l - 1;//终点
dp1[i][j] = 0x7fffffff; dp2[i][j] = 0;
for(int k = i; k < j; k++) {//枚举中间位置
dp1[i][j] = min(dp1[i][j], dp1[i][k] + dp1[k + 1][j]);//合并与不合并的最值
dp2[i][j] = max(dp2[i][j], dp2[i][k] + dp2[k + 1][j]);
}
dp1[i][j] = dp1[i][j] + sum[j] - sum[i - 1];
dp2[i][j] = dp2[i][j] + sum[j] - sum[i - 1];
}
}
int ans1 = 0x7fffffff, ans2 = 0;
for(int i = 1; i <= n; i++) ans1 = min(ans1, dp1[i][i + n - 1]);
for(int i = 1; i <= n; i++) ans2 = max(ans2, dp2[i][i + n - 1]);
cout << ans1 << endl << ans2 << endl;
return 0;
}
四、树形动态规划
1、分为左子树与右子树
P2015 二叉苹果树
/此代码有问题,待修改/
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 505;
int mapp[maxn][maxn];//表示i到j的苹果数
ll l[maxn], r[maxn];//第i点的左子树与右子树
ll a[maxn];//a[i]为i节点到其父亲的边权值
ll dp[maxn][maxn];//表示以i为根节点的数上保留j个节点的最大权值
ll n, q;
void addtree(int v) {//建左子树与右子树
for(int i = 1; i <= n; i++) {
if(mapp[v][i] != -1) {
l[v] = i; a[i] = mapp[v][i];
mapp[v][i] = -1;
mapp[i][v] = -1;
addtree(i);
break;
}
}
for(int i = 1; i <= n; i++) {
if(mapp[v][i] != -1) {
r[v] = i; a[i] = mapp[v][i];
mapp[v][i] = -1;
mapp[i][v] = -1;
addtree(i);
break;
}
}
}
int DP(int i, int j) {
if(j == 0) return 0;
if((l[i] == 0) && (r[i] == 0)) return a[i];//如果左右子节点都没有,代表这个点为一个叶子节点
if(dp[i][j] != 0) return dp[i][j];//已经计算
for(int k = 0; k <= j - 1; k++) {
dp[i][j] = max(dp[i][j], DP(l[i], k) + DP(r[i], j - k - 1) + a[i]);
//比较剪掉这条边和不剪这条边的最大值
return dp[i][j];
}
}
int main() {
cin >> n >> q;
q++;//保留的点数 = 保留的边数 + 1
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
mapp[i][j] = -1;//初始化
}
}
for(int i = 1; i <= n - 1; i++) {
int x, y, z;
cin >> x >> y >> z;
mapp[x][y] = z; mapp[y][x] = z;//双向
}
addtree(1);
cout << DP(1, q) << endl;
}
2、背包类树型DP
P2014 选课
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1005;
vector <int> son[maxn];//把选课的先修后修关系转化为图
#define ll long long
ll dp[maxn][maxn];//dp[i][j]表示在i为根的子树中选j门课的最大学分
ll s[maxn];
int m, n;
void DP(int x) {
dp[x][0] = 0;
for(int i = 0; i < son[x].size(); i++) {
int y = son[x][i];
DP(y);
for(int t = m; t >= 0; t--)//循环遍历当前课程总数,即背包容量
for(int j = t; j >= 0; j--)
dp[x][t] = max(dp[x][t], dp[x][t - j] + dp[y][j]);
}
if(x != 0) {//x不为0时,选修x本身需要占用1们课,并获得相应学分
for(int t = m; t > 0; t--)
dp[x][t] = dp[x][t - 1] + s[x];
}
}
int main() {
cin >> n >> m;
for(int i = 1; i <= n; i++) {
int x;
cin >> x >> s[i];
son[x].push_back(i);//表示修x必须先修i,即x为i的父节点
}
memset(dp, 0xcf, sizeof(dp));//0xcf即为-INF
DP(0);//从0节点开始,不需要先修任何课程
cout << dp[0][m] << endl;
return 0;
}