动态规划在我的理解中就是当前状态可以通过已知的状态来推出,也就是常说的最优子结构
背包问题
01背包
简单来讲就是有n件物体,每件物品由价值vi和重量wi,背包容量为m,每件物品最多只能取一次,求最大价值。
用dp[i][j]来表示在考虑前i件物品,容量为j的情况下所能取得的最大价值。 对于第i件物品,有取和不取两种情况,在j<wi的情况下就只能不取了。
1 取
dp[i] [j] = dp[i-1] [j-w[i]] + v[i];也就是说等于取i-1件物品容量为j-w[i]的情况下最大价值加上第i件物品的价值
2 不取
dp[i] [j] = dp[i-1] [j];
最终状态转移方程为dp[i] [j] = max(dp[i-1] [j-w[i]] + v[i] , dp[i] [j] = dp[i-1] [j]);
for(int i=1;i<=n;i++){
for(int j=w[i];j<=m;j++){
dp[i][j] = max(dp[i-1][j-w[i]] + v[i], dp[i-1][j]);
}
}
但我们可以发现每次更新的时候i总是只与i-1有关,所以我们可以压缩掉i这一维的空间,但是
dp[i] [j] = dp[i-1] [j-w[i]] + v[i]的时候j-w[i]是用到了之前的数据,所以在第二层循环的时候需要逆序遍历。
for(int i=1;i<=n;i++){
for(int j=m;j>=w[i];j--){
dp[j] = max(dp[j-w[i]] + v[i], dp[j]);
}
}
送快弟
现在我们有N个配件,他们有不同的价值. 但是我们背包的容量是有限的,因为我们只有一个一级包, 所以我们最多可以装V重量的东西. 但是为了能更好的吃到鸡(不存在的)我们要携带更有价值的配件,请问我们最多能拿多少价值的配件来当快递员呢??
input
输入的第一行是T, 表示有一共要打T场比赛.
每组数据由三行组成.
第一行包含两个整数N和V(N <= 1000, V <= 1000). N表示配件的个数, V表示一级包的大小(系统会更新嘛).
第二行包含N个整数, 表示每一个配件的价值.
第三行包含N个整数, 表示每个配件的重量.
ouput
对每一组数据, 输出我们最多能拿多少价值的配件.
Sample Input 1 10 10 1 3 5 7 9 11 13 15 17 19 19 17 15 13 11 9 7 5 3 1
Sample Output 51
经典01背包,代码如下
#include <iostream>
using namespace std;
#include <algorithm>
#include <string.h>
int n, v;
struct {
int val;
int wei;
} node[1005];
int dp[1005][1005];
int main()
{
int t;
cin >> t;
while(t--){
memset(dp,0,sizeof(dp));
cin >> n >> v;
for (int i = 1; i <= n; i++) cin >> node[i].val;
for (int i = 1; i <= n; i++) cin >> node[i].wei;
for (int i = 1; i <= n; i++){
for (int j = 0; j <= v;j++){
if(j<node[i].wei)
dp[i][j] = dp[i - 1][j];
else
dp[i][j] = max(dp[i - 1][j], dp[i-1][j - node[i].wei] + node[i].val);
}
}
cout << dp[n][v] << endl;
}
}
CD(背包路径)
You have a long drive by car ahead. You have a tape recorder, but unfortunately your best music is on CDs. You need to have it on tapes so the problem to solve is: you have a tape N minutes long. How to choose tracks from CD to get most out of tape space and have as short unused space as possible.
Assumptions:
- number of tracks on the CD. does not exceed 20
- no track is longer than N minutes
- tracks do not repeat
- length of each track is expressed as an integer number
- N is also integer
Program should find the set of tracks which fills the tape best and print it in the same sequence as the tracks are stored on the CD
input
Any number of lines. Each one contains value N, (after space) number of tracks M and durations of the tracks. For example from first line in sample data: N=5, M=3, first track lasts for 1 minute, second one 3 minutes, next one 4 minutes
The input data satisfies the following constraints:
N≤10000
M≤20
ouput
Set of tracks (and durations) which are the correct solutions and string ```sum:`" and sum of duration times.
Sample Input 5 3 1 3 4 10 4 9 8 4 2 20 4 10 5 7 4 90 8 10 23 1 2 3 4 5 7 45 8 4 10 44 43 12 9 8 2
Sample Output 1 4 sum:5 8 2 sum:10 10 5 4 sum:19 10 23 1 2 3 4 5 7 sum:55 4 10 12 9 8 2 sum:45
大致是求在m个数中某些数的和最接近但小于n,并求出其路径
#include <iostream>
using namespace std;
#include <vector>
#include <math.h>
#include <cstdio>
#include <stdio.h>
#include <algorithm>
#include <string.h>
typedef long long ll;
vector <int> ans[10005];
int dp[10005];
int num[10005];
bool flag[25][10005];
int main(){
int sum,n;
while(cin >> sum >> n){
memset(flag,0,sizeof(flag));
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;i++){
cin >> num[i];
}
//sort(num+1,num+1+n,less<int>());
for(int i=n;i>=1;i--){
for(int j=sum;j>=num[i];j--){
//dp[j] = max(dp[j],dp[j-num[i]]+num[i]);
if(dp[j-num[i]]+num[i]>dp[j]){
dp[j] = dp[j-num[i]]+num[i];
flag[i][j] = 1;
}
}
}
for(int i = 1, j = dp[sum]; i <= n,j>0; i++){
if(flag[i][j]) {
cout << num[i] <<" ";
j -= num[i];
}
}
cout << "sum:" << dp[sum] << endl;
}
}
无限背包
与01唯一的不同在于每件物品是可以无限拿取的
代码差不多,只是在二层循环的时候需要正序遍历
多重背包(待补)
有限制条件的背包(待补)
区间dp
区间DP,指的就是对区间的DP,主要的思想是依旧是最优子结构和无后效性的确保, 一般思路就是先对小区间进行操作得到最优解, 然后通过小区间的最优解来得到大区间的最优解。(copy而来)
基本模板
for(int len=2;len<=n;len++){//先枚举长度
for(int l=1,r=l+len-1;r<=n;l++,r++){//再枚举l
for(int k=l+1;k<r;k++){//后枚举分界点
dp[l][r] = min(dp[l][r],dp[l][k]+dp[k+1][r]);
}
}
}
//时间复杂度为n^3^
石子合并
题目描述
在一个圆形操场的四周摆放 N 堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。
试设计出一个算法,计算出将 N 堆石子合并成 1 堆的最小得分和最大得分。
输入格式
数据的第 11 行是正整数 N,表示有 N 堆石子。
第 22 行有 N个整数,第 i 个整数 ai 表示第 i堆石子的个数。
输出格式
输出共 22 行,第 11 行为最小得分,第 22 行为最大得分。
输入 #1
4
4 5 9 4
输出 #1
43
54
区间dp入门模板题,需要注意的是这里是呈环形,常用的处理方式是把环分成链,这就需要扩展n到2n,使a[i+1]=a[1]…
#include <iostream>
using namespace std;
#include <algorithm>
#include <cstdio>
#include <queue>
#include <vector>
#include <stack>
#include <string.h>
#include <string>
#include <math.h>
#include <map>
#include <unordered_map>
#include <stdlib.h>
#include <set>
#include <unordered_set>
#include <bitset>
#define IOS std::ios::sync_with_stdio(false);
#define INF 0x3f3f3f3f
typedef long long ll;
typedef unsigned long long ull;
const int N = 1e2+5;
const int mod = 1000000007;
int dp_min[N][N];
int dp_max[N][N];
int pre[N];
int a[N];
int main(){
memset(dp_min,INF,sizeof(dp_min));
int n;
cin >> n;
for(int i=1;i<=n;i++)
cin >> a[i],pre[i] = pre[i-1]+a[i];
for(int i=n+1;i<=2*n;i++) a[i] = a[i-n],pre[i] = pre[i-1]+a[i];
for(int i=0;i<=2*n;i++) dp_min[i][i] = 0;
for(int len=2;len<=n;len++){
for(int l=1,r=l+len-1;r<=n+n;l++,r++){
for(int k=l;k<r;k++){
dp_min[l][r] = min(dp_min[l][r],dp_min[l][k]+dp_min[k+1][r]+(pre[r]-pre[l-1]));
dp_max[l][r] = max(dp_max[l][r],dp_max[l][k]+dp_max[k+1][r]+(pre[r]-pre[l-1]));
}
}
}
int ans_min = 1e9,ans_max = 0;
for(int s=1;s<=n;s++){
ans_min = min(ans_min,dp_min[s][s+n-1]);
ans_max = max(ans_max,dp_max[s][s+n-1]);
}
cout << ans_min << endl << ans_max;
return 0;
}
四边形优化
n^3实在是太大了,在一些数据很小的情况下可以过,但数据较大时是不可以接受的。
第三层循环中寻找分界点的时候是有很多重复的,我们可以用一个s[i] [j]的数组来记录dp[i] [j]的最佳分割点的下标,因为s数组满足四边形不等式所以dp[i] [j]k的范围为s[i] [j-1]到dp[i+1] [j]。综合下来时间复杂度为n^2。
s的初始化令s[i] [i] =i就行了 (我也不太确定)