动态规划问题:01背包,最长上升子序列,区间dp,概率dp
递推:拆分成子问题,解决原问题
辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”
如果你是辰辰,你能完成这个任务吗?
第一行有 2 2 2 个整数 T T T( 1 ≤ T ≤ 1000 1 \le T \le 1000 1≤T≤1000)和 M M M( 1 ≤ M ≤ 100 1 \le M \le 100 1≤M≤100),用一个空格隔开, T T T 代表总共能够用来采药的时间, M M M 代表山洞里的草药的数目。
接下来的 M M M 行每行包括两个在 1 1 1 到 100 100 100 之间(包括 1 1 1 和 100 100 100)的整数,分别表示采摘某株草药的时间和这株草药的价值。
输出在规定的时间内可以采到的草药的最大总价值。
70 3
71 100
69 1
1 2
3
【数据范围】
【题目来源】
NOIP 2005 普及组第三题
1.记忆化搜索
考虑第 i i i个草药
如果采第 i i i个草药,则需要知道时间为 j − t i j-t_i j−ti之前采了 i − 1 i-1 i−1个草药所得到的最大价值
如果不采第 i i i个草药,则需要知道时间为 j j j之前采了 i − 1 i-1 i−1个草药所得到的最大价值
所以rem[i][j]来表示选择采前 i i i个草药的几种在时间 j j j内所能达到的最大价值
2.递推
r e m [ i ] [ j ] = { r e m [ i − 1 ] [ j ] v[i] > j m a x ( r e m [ i − 1 ] [ j ] , r e m [ i − 1 ] [ j − v [ i ] ] + t [ i ] ) v[i] ≤ j rem[i][j]= \begin{cases} rem[i-1][j] & \text{v[i] > j} \\ max(rem[i-1][j], rem[i-1][j-v[i]]+t[i]) & \text{v[i] $\leq$ j}\\ \end{cases} rem[i][j]={rem[i−1][j]max(rem[i−1][j],rem[i−1][j−v[i]]+t[i])v[i] > jv[i] ≤ j
起始条件 r e m [ 0 ] [ j ] = 0 rem[0][j]=0 rem[0][j]=0
#include
using namespace std;
int T, M;
struct Cao{
int time;
int value;
bool operator< (Cao b){
return time > b.time || (time==b.time && value > b.value);
}
bool operator> (Cao c) const{
return time > c.time || (time==c.time && value > c.value);
}
}C[105];
int ans;
int res[105][1005];
int dfs(int i, int j){
if(res[i][j]!=-1) return res[i][j];
if(i==0) ans=0;
else if(C[i].time > j) ans=dfs(i-1, j);
else ans = max(dfs(i-1, j), dfs(i-1, j-C[i].time)+C[i].value);
res[i][j]=ans;
return ans;
}
int main(){
cin >> T >> M;
for(int i = 1; i <= M; i++){
cin >> C[i].time >> C[i].value;
}
memset(res, -1, sizeof(res));
sort(C+1, C+M+1, greater<Cao>());
dfs(M, T);
cout << ans;
}
注意:大于号重载时要加const,否则编译错误~
#include
using namespace std;
int T, M;
struct Cao{
int time;
int value;
}C[105];
int ans;
int res[105][1005];
int main(){
cin >> T >> M;
for(int i = 1; i <= M; i++){
cin >> C[i].time >> C[i].value;
}
memset(res, 0, sizeof(res));
for(int i = 1; i <= M; i++){
for(int j = 0; j <= T; j++){
res[i][j]=res[i-1][j];
if(j >= C[i].time)
res[i][j]=max(res[i][j], res[i-1][j-C[i].time]+C[i].value);
}
}
cout << res[M][T];
}
这是一个简单的动规板子题。
给出一个由 n ( n ≤ 5000 ) n(n\le 5000) n(n≤5000) 个不超过 1 0 6 10^6 106 的正整数组成的序列。请输出这个序列的最长上升子序列的长度。
最长上升子序列是指,从原序列中按顺序取出一些数字排在一起,这些数字是逐渐增大的。
第一行,一个整数 n n n,表示序列长度。
第二行有 n n n 个整数,表示这个序列。
一个整数表示答案。
6
1 2 4 1 3 4
4
分别取出 1 1 1、 2 2 2、 3 3 3、 4 4 4 即可。
考虑第 i i i个是否可以加入到上升子序列
遍历前 i − 1 i-1 i−1个,如果当前数大(可以加入上升子序列中),则比较当前的子序列与构成的新子序列谁的元素多,并更新
选取最长的上升子序列
#include
using namespace std;
int res[5005];
int list1[5005];
int n;
int main(){
cin >> n;
for(int i = 0; i < n; i++) cin >> list1[i];
for(int i = 0; i < n; i++) res[i]=1;
for(int i = 1; i < n; i++){
for(int j = 0; j < i; j++){
if(list1[i]>list1[j]) res[i]=max(res[i], res[j]+1);
}
}
sort(res, res+n);
cout << res[n-1];
}
给出一个长度为 n n n 的序列 a a a,选出其中连续且非空的一段使得这段和最大。
第一行是一个整数,表示序列的长度 n n n。
第二行有 n n n 个整数,第 i i i 个整数表示序列的第 i i i 个数字 a i a_i ai。
输出一行一个整数表示答案。
7
2 -4 3 -1 2 -4 3
4
选取 [ 3 , 5 ] [3, 5] [3,5] 子段 { 3 , − 1 , 2 } \{3, -1, 2\} {3,−1,2},其和为 4 4 4。
递推,考虑第 i i i个的最大子序列和
发现只与第 i − 1 i-1 i−1个有关
比较把第 i i i个加入子序列和把第 i i i个当作第一个子序列元素,哪一个和最大
#include
using namespace std;
const int Max = 2*1e5+5;
int n;
int list1[Max];
int res[Max];
int main(){
cin >> n;
for(int i = 0; i < n; i++){
cin >> list1[i];
}
memset(res,0,sizeof(res));
res[0]=list1[0];
for(int i = 1; i < n; i++){
res[i]=max(res[i-1]+list1[i],list1[i]);
}
sort(res,res+n);
cout << res[n-1];
}
给定一个字符串 s s s 和一个字符串 t t t ,输出 s s s 和 t t t 的最长公共子序列。
两行,第一行输入 s s s ,第二行输入 t t t 。
输出 s s s 和 t t t 的最长公共子序列。如果有多种答案,输出任何一个都可以。
axyb
abyxb
axb
aa
xayaz
aa
a
z
abracadabra
avadakedavra
aaadara
数据保证 s s s 和 t t t 仅含英文小写字母,并且 s s s 和 t t t 的长度小于等于3000。
二维数组res[i][j] a的前 i i i个元素和b的前 j j j个元素中,最长公共子序列的长度。
递推:
r e s [ i ] [ j ] = { r e m [ i − 1 ] [ j − 1 ] + 1 a[i]=b[j] m a x ( r e m [ i − 1 ] [ j ] , r e m [ i ] [ j − 1 ] ) a[i] ≠ b[j] res[i][j]= \begin{cases} rem[i-1][j-1]+1 & \text{a[i]=b[j]} \\ max(rem[i-1][j], rem[i][j-1]) & \text{a[i]$\neq$b[j]}\\ \end{cases} res[i][j]={rem[i−1][j−1]+1max(rem[i−1][j],rem[i][j−1])a[i]=b[j]a[i]=b[j]
难点:得到子序列元素
如果a[i]=b[j] ,那么刚好这个元素就是当前子序列第res[i][j]个的元素;
如果不相等,看res[i][j]是从哪来的,就退回去,直到上一条件的时候
#include
using namespace std;
const int Max = 3005;
int res[Max][Max];
string a,b;
char ans[Max];
int main(){
cin >> a;
cin >> b;
int na = a.length();
int nb = b.length();
memset(res, 0, sizeof(res));
for(int i = 1; i <= na; i++){
for(int j = 1; j <= nb; j++){
if(a[i-1]==b[j-1]){
res[i][j] = res[i-1][j-1]+1;
}
else{
res[i][j] = max(res[i-1][j], res[i][j-1]);
}
}
}
int num = res[na][nb];
while(res[na][nb]>0){
int ind = res[na][nb];
if(a[na-1]==b[nb-1]){
ans[ind-1] = a[na-1];
na--;
nb--;
}
else{
if(res[na][nb]==res[na-1][nb]) na--;
else nb--;
}
}
for(int i = 0; i < num; i++){
cout << ans[i];
}
}
记录一个经常犯得小错误:总把 j j j 写成 i i i (太致命啦~)