动态规划(01背包、完全背包、多重部分和、LCS、LIS、划分数、多重集组合数)
来源:互联网 发布:linux ubuntu安装 rpm 编辑:程序博客网 时间:2024/06/10 20:04
编辑器存在大小不一的情况,更好的阅读体验可戳WizNote:动态规划
01背包
dp[i][j]:从第i个物品开始挑选总重小于j时,总价值的最大值 O(nW)
(i逆序,j顺序)
i:n-1~0 j:0~W
1.没有剩余物品(i<0)
2.无法挑选这个物品(j<w[i] dp[i][j] = dp[i+1][j])
3.如果可以挑选 选择是否挑选 (dp[i][j] = max(dp[i+1][j],dp[i+1][j-w[i]]+v[i])
dp[i][j]:前i个物品总重量不超过j 的物品时 总价值的最大值
(i顺序,j顺序)
i:1~n j:0~W
★初始化dp[0][j] = 0(dp[i-1][j]状态时会用到 并且 i=0时相当于没有选取物品)
1.没有剩余物品(i>n)
2.无法挑选这个物品(j<w[i] dp[i][j] = dp[i-1][j])
3.如果可以挑选 选择是否挑选(dp[i][j] = max(dp[i-1][j],dp[i-1][j-w[i]]+v[i] )
入门题:HDU 2602
#include <iostream>#include <cstdio>#include <cstring>#include <algorithm>using namespace std;const int N = 1e3 + 10;int dp[N][N];int w[N], v[N];int main() {int t;scanf("%d", &t);while(t--) {memset(dp, 0, sizeof(dp));int n, V;scanf("%d %d", &n, &V);for(int i = 1; i <= n; i++) {scanf("%d", &v[i]);}for(int i = 1; i <= n; i++) {scanf("%d", &w[i]);}for(int i = 1; i <= n; i++) {for(int j = 0; j <= V; j++){if(j < w[i]){dp[i][j] = dp[i - 1][j];}else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]);}}printf("%d\n", dp[n][V]);}}
最长公共子序列(LCS)
s1~sn t1~tn
dp[i][j] :s1...si和t1...tj对应的LCS长度
s1...si+1和t1..ti+1对应的公共子序列可能是
si+1=ti+1时,在s1..si和t1...tj的LCS末尾追加上si+1
s1..si和t1..tj+1的LCS
s1..si+1和t1..tj的LCS
dp[i+1][j+1]=dp[i][j]+1(si+1=tj+1)
=max(dp[i][j+1],dp[i+1][j]) (其他)
入门题:POJ 1458
#include <iostream>#include <cstdio>#include <cstring>#include <algorithm>using namespace std;const int N = 1e3 + 10;char s[N], t[N];int dp[N][N];int main(){while(~scanf("%s %s", s, t)){memset(dp, sizeof(dp), 0);int n = strlen(s);int m = strlen(t);for(int i = 0; i < n; i++){for(int j = 0; j < m; j++){if(s[i] == t[j]){dp[i+1][j+1] = dp[i][j] + 1;}else{dp[i+1][j+1] = max(dp[i][j+1], dp[i+1][j]);}}}printf("%d\n", dp[n][m]);}}
完全背包
dp[i][j]:前i个物品总重量不超过j 的物品时 总价值的最大值
dp[0][j]=0
i:1~n j:0~W k:0~k*w[i]<j
dp[i][j]=max(dp[i][j],dp[i-1][j-k*w[i]]+k*v[i])(dp[i][j]是因为k循环)O(nW2)
但是在计算dp[i][j]中选择k个的情况时,已经在dp[i][j-w[i]]中选择k-1个计算过(w[i]恰好为第k个)
即dp[i][j]递推中k≥1的情况都已经被计算过
因此可以变形为
max{dp[i-1][j-k*w[i]]+k*v[i](k≥0)}
=max(dp[i-1][j],max{dp[i-1][j-k*w[i]]+k*v[i](k≥1)})
=max(dp[i-1][j],max { dp[i-1][( j-w[i]) - k*w[i]]+k*v[i](k≥0)}+v[i])
=max(dp[i-1][j],dp[i][j-w[i]]+v[i])
dp[i][j]=dp[i-1][j](j<w[i]) O(nW)
=max(dp[i-1][j],dp[i][j-w[i]]+v[i])
本质是当前的状态选取k个时,直接从当前选取k-1个的状态转移过来
入门题:HDU 1248
#include <iostream>#include <cstdio>#include <cstring>#include <algorithm>using namespace std;const int N = 1e4 + 10;int dp[5][N];int main(){int w[4] = {0, 150, 200, 350};int t,W;scanf("%d", &t);while(t--){memset(dp, 0, sizeof(dp));scanf("%d", &W);for(int i = 1; i <= 3; i++){for(int j = 0; j <= W ;j++){if(j < w[i]){dp[i][j] = dp[i - 1][j];}else dp[i][j] = max(dp[i - 1][j], dp[i][j - w[i]] + w[i]);}}printf("%d\n", W - dp[3][W]);}}
一维数组的实现(空间复杂度降低)
01背包:
i:1~n j:W~w[i] dp[j]=max(dp[j],dp[j-w[i]]+v[i])
完全背包:
i:1~n j:w[i]~W dp[j]=max(dp[j],dp[j-w[i]]+v[i])
★ 只有循环的方向不同
多重部分和问题:
ai mi K
dp[i+1][j]:用前i种数加和得到j时 第i种数最多能剩余多少个 O(nK)
1. 前面已经加到j dp[i+1][j]=m[i] dp[i][j]≥0
2. 不能加的情况:当前a[i]>j 或 前i+1个数加到j-a[i]时已经没有剩余 dp[i+1][j-a[i]]≤0
3. 前面已经加到 j-a[i] 这里还需要一个a[i] dp[i+1][j]=dp[i+1][j-a[i]]-1
+数组重复利用:
int dp[MAX_K + 1];
void solve() {
memset(dp, -1, sizeof(dp));
dp[0] = 0;
for (int i = 0; i < n; i++){
for (int j = 0; j < W; j++){
if (dp[j] >= 0) dp[j] = m[i];
else if (j < a[i] || dp[j - a[i]] <= 0) dp[j] = -1;
else dp[j] = dp[j - a[i]] - 1;
}
}
if(dp[K] >= 0) puts("YES");
else puts("NO");
}
最长上升子序列(LIS)
a[i] n
dp[i]:长度为i的上升子序列中末尾元素的最小值(没有则不存在) O(nlogn)
j:1~n i:1~n
用INF填充 表示不存在
1. j==1 dp[i]=a[j]
2.直到找到dp[i-1]<a[j] 找到a[j]>dp[i-1]的位置的后一位,把当前位置的dp[i]替换成min(dp[i],a[i])
因为第二步查找可以用二分 所以可把原来为n2的复杂度降为nlogn
*lower_bound(dp,dp+n,a[i]) ai-1<ai≤ai+1
*upper_bound: ai-1<=ai<ai+1
入门题:HDU 5748
#include <iostream>#include <cstdio>#include <cstring>#include <algorithm>using namespace std;const int N = 1e5 + 10;const int INF = 0x3f3f3f3f;int a[N], b[N];int dp[N];int main(){int t;scanf("%d", &t);while (t--){int n;scanf("%d", &n);memset(dp, INF, sizeof(dp));for (int i = 1; i <= n; i++){scanf("%d", &a[i]);}for (int i = 1; i <= n; i++){*lower_bound(dp, dp + n, a[i]) = a[i];b[i]=lower_bound(dp, dp + n, a[i]) - dp + 1;}for (int i = 1; i < n; i++){printf("%d ", b[i]);}printf("%d\n", b[n]);}}
需要注意的是,该题是让求一个b[i],使得LIS(b[i])=a[i]且b[i]要字典序最小,其实就是得到每个位置上的LIS。
每个位置在用lower_bound求取时也已经可以得到
注意lower_bound和upper_bound的用法即可。
计数问题dp:
划分数:n个无区别物品划分成m组
dp[i][j]:j的i划分总数
每一步有两种选择
1)用掉当前最大数 减去之后继续
2)不用了 最大数-1;
int f(int n,int idx){
if(n==0) return 1;
if(idx<=0) return 0;
return f(n-idx,idx)+f(n,idx-1);
}
i:1~n j:1~m
dp[i][j]=dp[i-j][j]+dp[i][j-1] (O(nm))
多重集组合数:n种物品 每种ai个 (不同种类物品不同 同种物品相同) 从中取出m个
dp[i+1][j]:从前i种物品中取出j种的方案数
每一个dp[i+1][j] 都是由从前i-1种物品取东西转移过来 dp[i][j-0]+dp[i][j-1]+...+dp[i][j-min(ai,j)]
dp[i+1][j]=dp[i][j-0]+dp[i][j-1]+...+dp[i][j-min(ai,j)]=dp[i][j]+dp[i][j-1]+dp[i][j-1-1]+dp[i][j-1-2]+...+dp[i][j-1-(min(ai,j)-1)]
=dp[i][j]+dp[i+1][j-1]-dp[i][j-1-min(ai,j)]
i:0~n
j:0~m
题目可戳:POJ 3046
0 0
- 动态规划(01背包、完全背包、多重部分和、LCS、LIS、划分数、多重集组合数)
- 动态规划 多重部分和 最长上升子序列 划分数 多重集组合数
- 动态规划总结(01背包 完全背包 多重背包)
- 动态规划初步( 01 背包、完全背包、多重背包)
- 动态规划-----背包问题-----01背包,完全背包,多重背包
- 动态规划之01背包,完全背包,多重背包
- 动态规划之01背包,完全背包,多重背包模板
- 【动态规划】完全背包、多重背包
- 动态规划(多重背包)
- 多重背包(动态规划)
- 多重集组合数(动态规划(DP))
- 动态规划--背包问题(0-1背包,完全背包,多重背包)
- 01背包 ,完全背包,多重背包 dp (动态规划入门dp)
- 背包问题之01背包、完全背包和多重背包
- 01,完全,多重背包
- 背包问题(01背包,完全背包,多重背包)
- 背包问题(01背包,完全背包,多重背包)
- 背包(01背包、完全背包、多重背包)问题总结
- 树形排序与堆排序
- IPython(jupyter)
- 我就是我
- 最短路径最大流的SAP算法
- Java集合框架学习---1.ArrayList和LinkedList
- 动态规划(01背包、完全背包、多重部分和、LCS、LIS、划分数、多重集组合数)
- 类(2)
- QCache 缓存
- 性能优化二 高性能的索引策略
- 生成树的计数Matrix-Tree定理
- 利用urllib+beadutifulsoup编写自己的第一个小爬虫,获取美女图片
- layer-list实现阴影和选择器效果
- 正规式转确定有穷自动机(NFA)
- CCF消除类游戏JAVA答案