一,了解DP
解决多阶段决策最优化问题的方法
把所给求解问题的过程恰当地分成若干个相互联系的阶段
这个过程有点像分治法,但是不同于分治的是,分治是将问题分为子问题再逐个求解,而动态规划是将问题考虑当前情况再依次判断相关有联系的阶段,有点像树的结构。
用最简单的背包问题举例:
题:A有一个最大承重量为m的书包,一共有n个物品,分别的重量和价值为a,b,请问A能拿的最大价值是多少?
例如:
m=8
序号 1 2 3 4
重量 4 3 3 5
价值 7 1 8 2
初始值dp[1][8]
从1开始,可以选择拿或者不拿
如果拿则变成了dp[1][8-4]+7
如果不拿则是dp[1][8]+0
到2
1)第一步拿了
如果拿dp[2][4-3]+7+1
不拿 dp[2][4]+7
2)第一步没那
如果拿dp[2][8-3]+1
不拿dp[2][8]+0
到3
1)第一步拿了,第二步没拿····
·······
·······
这样我们就可以求出每一个情况的最终价值,从而比较得出最大价值。
也可以画成二叉树更加形象。
因此dp解决的也是一个前后关联具有链状结构的多阶段过程
看到这我们也能想到dp于递归有点像,其实dp与递归在一部分题中也是能够互相转化的。
二,题练
洛谷P1757 通天之分组背包
这里区分于上面讲的背包问题在于有无分组标号,该题加入了标号,因此在写的时候要注意每一个组中只能取一个。那么我们就要考虑应该怎样才能在选取的时候使每一个包都能取到。
&emdp;首先在输入的时候把重量和价值分别存入w【】和v【】中,再计算各个标号里面有几个数组用cnt【】++实现,最后再将dp【i】【j】=k存入数据,其中i表示组数,j表示在该组中的序次,k即dp【】【】表示该数在输入时的位置,即抛开组数的次序。
我们采取案例来示范:
m=45,n=3
10 10 1
10 5 1
50 400 2
cnt[1]=2 cnt[2]=1
dp[1][1]=1 dp[1][2]=2 dp[2][1]=3
进入r循环
i=1 -> j=m -> k=1 -> px=dp[1][1]=1 -> j=m=45>=w[1]=10
-> f[45]=max(f[45-10]+10,f[45]=0)=f[35]+10
k++<=2 -> px=2 -> j=45 >=10 -> f[45]=max(f[45-10]+5,f[45]) 注意:这里的f[45]不再是0而是上面求出来的f[35]+10
所以这里的值应该取上面的f[45]
j–遍历重量,这里可以求f[10]~f[34]
j=m-1 -> k=1 -> px=1 -> j=44>=10 -> f[44]=max(f[44-10]+10,f[44]) f[44]=0,所以f[44]=f[34]+10=10
j=m-1 -> k=2 -> px=2 -> j=44>=10 -> f[44]=max(f[44-10]+5,f[44]) f[44]=f[34]+10,所以f[44]=f[34]+10=10
j=m-2 -> k=1 -> px=1 -> j=43>=10 -> f[43]=max(f[43-10]+10,f[43]) f[43]=0,所以f[43]=f[33]+10=10
·······
j=10 -> k=1 -> px=1 -> j=10>=10 -> f[10]=max(f[0]+10,f[10]) f[10]=0,所以f[10]=f[0]+10=10
j=10 -> k=2 -> px=2 -> j=10>10 -> f[10]=max(f[0]+5,f[10]) f[10]=f[0]+10,所以f[10]=f[0]+10 =10
f[0]表示当剩余重量为0时的价值,此时已经不能再装下其它东西,所以f[0]=0 f[10]=10
i++ -> j=m -> k=1 -> px=3 -> j=45>50 X结束,
所以,这里f[j] = max(f[j - w[px]] + v[px], f[j]);的含义是什么呢?
其实就是判断同一组中取的最大价值元素!
比如1组中的a,b,c,求最适元素,同级取大即可!
源代码:
#include<iostream>
#include<cstring>
#include<cmath>
#include<cstdint>
#include<algorithm>
#include<climits>
#include<cstdlib>
#include<cstdio>
#include<ctime>
#include<map>
#include<queue>
#include<vector>
#include<set>
using namespace std;
#define ll long long
#define MAX 10e7+3
#define MIN 1e-9
#define r(i,j,k) for(int i=j;i<=k;i++)
#define dr(i,j,k) for(int i=j;i>=k;i--)
#define clear(a,i) memset(a,i,sizeof(a))
int dp[107][107];
int cnt[107];
int v[100000];
int w[101000];
int f[10000005];//表示最终价值
int main(void)
{
int m, n;
cin >> m >> n;
int p, nmb = 0;
r(i, 1, n)
{
cin >> w[i] >> v[i] >> p;
nmb = max(nmb, p);
cnt[p]++;
dp[p][cnt[p]] = i;
}
r(i, 1, nmb)//i遍历组数
{
dr(j, m, 0)//j表示重量的剩余值
{
r(k, 1, cnt[i])//表示组数中的个数遍历
{
int px = dp[i][k];//px表示在大环境下的次序
if (j >= w[px])//如果j》w该情况下表示w没有超出限制进入条件
{
f[j] = max(f[j - w[px]] + v[px], f[j]);
//j-w[px]表示剩余的重量,
//f[j-w[px]]表示剩余重量里面的价值,
//f[]+v[]表示如果取值后的价值,
//最后max(f[···],f[])取最大
}
}
}
}
cout << f[m];
return 0;
}