模型:
最长上升子序列
朴素做法:
for (int i = 1; i <= n; i++) {
dp[i] = 1;
for (int j = 1; j < i; j++) {
if (a[j] < a[i]) {
dp[i] = max(dp[i], dp[j] + 1);
}
}
}
for (int i = 1; i <= n; i++) {
ans = max(ans, dp[i]);
}
dp优化
int dp[NMAX], len;
dp[len = 1] = a[1];
for(int i = 2;i <= n;i += 1) {
if(a[i] > dp[len]) {
dp[++len] = a[i];
} else {
*lower_bound(dp + 1, dp + len + 1, a[i]) = a[i];
//解引用这个地址
}
}
当然了,你也可以选择树状数组优化
最长公共子序列
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]);
}
}
}
你要怎么求出转移方程
for (int i = 1; i <= a.size(); i++) {
dp[i][0] = i;
}
for (int i = 1; i <= b.size(); i++) {
dp[0][i] = i;
}
for (int i = 1; i <= a.size(); i++) {
for (int j = 1; j <= b.size(); j++) {
if (a[i - 1] == b[j - 1]) {
dp[i][j] = dp[i - 1][j - 1];
} else {
dp[i][j] = min(min(dp[i - 1][j - 1], dp[i - 1][j]), dp[i][j - 1]) + 1;
}
}
}
背包问题
可拆分
struct Item {
int c, w;
};
Item item[1010];
bool cmp(Item a, Item b) {
return a.w * b.c > b.w * a.c; // 比较单位体积物品价值
}
int main() {
//省略部分输入代码
sort(item + 1, item + 1 + N, cmp); // 先进行排序
double ans = 0;
for(int i = 1; i <= N; i++) { // 单位体积价值越高越优先放入背包
if(V <= item[i].c) { // 背包将被当前物品装满
ans += (double)item[i].w / item[i].c * V;
V = 0; // 剩余容量变为 0
break; // 循环结束
} else { // 直接将物品放入背包
ans += item[i].w; // 总价值增加
V -= item[i].c; // 背包剩余容量减少
}
}
//省略部分输出代码
return 0;
}
01背包
for (int i = 1; i <= N; i++) {
for (int j = V; j >= c[i]; j--) { // 循环从背包容积 V 开始,到 c[i] 结束,从大到小枚举
dp[j] = max(dp[j], dp[j - c[i]] + w[i]);
// 对于 j 来说,更小的位置 j - c[i]
// 还未更新,里面存储的是上一层的值,因此可以进行计算
}
}
多重背包问题
状态压缩动态规划
int ncnt = 0;
// 二进制拆分
for (int i = 1; i <= N; ++i) {
int k;
// 找到最大的 k
for (k = 1; n[i] - (1 << k) + 1 > 0; ++k) {
nc[ncnt] = (1 << (k - 1)) * c[i];
nw[ncnt] = (1 << (k - 1)) * w[i];
++ncnt;
}
--k;
// 最后一组
nc[ncnt] = (n[i] - (1 << k) + 1) * c[i];
nw[ncnt] = (n[i] - (1 << k) + 1) * w[i];
++ncnt;
}
// 01 背包
for (int i = 0; i < ncnt; ++i) {
for (int j = V; j >= nc[i]; --j) {
dp[j] = max(dp[j], dp[j - nc[i]] + nw[i]);
}
}
完全背包问题
for (int i = 1; i <= N; i++) {
for (int j = c[i]; j <= V; j++) {
dp[j] = max(dp[j - c[i]] + w[i], dp[j]);
}
}
多维背包问题
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= V; j++) {
for (int k = 0; k <= W; k++) {
dp[i][j][k] = dp[i - 1][j][k];
if (j >= v[i] && k >= w[i]) {
dp[i][j][k] = max(dp[i][j][k], dp[i - 1][j - v[i]][k - w[i]] + c[i]);
}
}
}
}
分组背包
for(int i = 1; i <= d; ++i){ // 枚举组
for(int k = target; k >= 0; --k){ // 枚举背包体积进行转移
for(int j = 1; j <= f; ++j){ // 枚举组内的所有物品
if(k >= c[i][j])
dp[k] = max(dp[k], dp[k - c[i][j]] + v[i][j]); // 此时 dp[k-j] 一定表示上一组的状态
}
}
}
树形背包
本质上是一个树上的dfs
转移方程如下
f[i][j]=max{f[i+siz[i]][j],f[i+1][j−w[i]]+v[i]}.
也可以使用dfs序做
把从根开始搜索,将所有的点按照其搜索到的顺序排列到序列中,如果点i的DFS序为x,就表示是第x个被搜索到的
记录dfs序以及子树大小
DAG上的dp
DAG:有向无环图
需要搭配拓扑排序
int toposort() {
queue<int> q;
for (int i = 1; i <= n; ++i) {
if (deg[i] == 0) {
// 初始化 f[i]
q.push(i);
}
}
while (!q.empty()) {
int u = q.front();
q.pop();
for (int i = head[u]; i; i = G[i].next) {
int v = G[i].to;
// 用 f[u] 更新 f[v]
if (--deg[v] == 0) q.push(v); // v 的 DP 值已经计算完成,可以用于更新其他点
}
}
// 用每个点的 DP 值计算最终的答案
}