1.[NOIP2005 普及组] 采药
题目描述
辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”
如果你是辰辰,你能完成这个任务吗?
输入格式
第一行有 2 个整数 T(1 <= T <=1000)和 M(1 <= M <= 100),用一个空格隔开,T 代表总共能够用来采药的时间,M 代表山洞里的草药的数目。
接下来的 M 行每行包括两个在 1 到 100 之间(包括 1 和 100)的整数,分别表示采摘某株草药的时间和这株草药的价值。
输出格式
输出在规定的时间内可以采到的草药的最大总价值。
样例 #1
样例输入 #1
70 3
71 100
69 1
1 2
样例输出 #1
3
提示
【数据范围】
- 对于 30% 的数据,M <= 10;
- 对于全部的数据,M <= 100。
【题目来源】
NOIP 2005 普及组第三题
这道题跟背包问题非常相似,背包问题的限制是物品的重量,而这个问题是采一株草药的时间,结果都是问怎样才能采到最大价值的东西,所以思路都是一样的,设dfs[i][j]
为在i段时间内,j株草药内采到的草药的最大价值。那么状态转移方程为:
d
f
s
[
i
]
[
j
]
=
{
d
f
s
[
i
]
[
j
−
1
]
i
>
T
m
a
x
{
d
f
s
[
i
−
t
i
m
e
[
j
]
]
[
j
−
1
]
+
v
a
l
u
e
[
j
]
,
d
f
s
[
i
]
[
j
−
1
]
}
i
<
=
T
0
i
=
0
,
j
=
0
dfs[i][j]=\begin{cases} dfs[i][j-1] & i>T \\ max\{dfs[i-time[j]][j-1]+value[j],dfs[i][j-1]\} & i<=T \\ 0 & i=0,j=0 \end {cases}
dfs[i][j]=⎩
⎨
⎧dfs[i][j−1]max{dfs[i−time[j]][j−1]+value[j],dfs[i][j−1]}0i>Ti<=Ti=0,j=0
所以,根据公式,我们就能很快解决这个问题:
完整注释代码如下:
#include <bits/stdc++.h>
using namespace std;
int T,M;
int t[105],value[105],ret[1005][105]; //这里用ret[i][j]记录dfs[i][j]的值,减小算法复杂度
int dfs(int i,int j){ //dfs(i,j)表示在i段时间内,j组草药中能采到草药的最大价值
int ans;
if(ret[i][j]!=-1){
return ret[i][j];
}
if(i<=0||j==0){
ans=0;
}
else if(t[j]>i){
ans=dfs(i,j-1);
}
else{
ans=max(dfs(i-t[j],j-1)+value[j],dfs(i,j-1));
}
ret[i][j]=ans;
return ans;
}
int main(){
cin>>T>>M;
memset(ret,-1,sizeof(ret));
for(int i=1;i<=M;i++){
cin>>t[i]>>value[i];
}
cout<<dfs(T,M);
return 0;
}
2.最长上升子序列
题目描述
这是一个简单的动规板子题。
给出一个由 n(n<= 5000) 个不超过 10^6 的正整数组成的序列。请输出这个序列的最长上升子序列的长度。
最长上升子序列是指,从原序列中按顺序取出一些数字排在一起,这些数字是逐渐增大的。
输入格式
第一行,一个整数 n,表示序列长度。
第二行有 n 个整数,表示这个序列。
输出格式
一个整数表示答案。
样例 #1
样例输入 #1
6
1 2 4 1 3 4
样例输出 #1
4
提示
分别取出 1、2、3、4 即可。
这道题肯定是可以用搜索来做的,直接爆搜,把每种方法都是一遍。但显然,这里的数据太大,是肯定会TLE的,所以,我们采取效率更高的动态规划来做~~(这不是废话吗?作业名不就叫动态规划吗?)~~。所以我们不妨设dfs[i]
为以第i个数为结尾的最长上升子序列的长度,那么这里动态转移方程我们就能写出来:
d
f
s
[
i
]
=
{
m
a
x
{
d
f
s
[
i
]
,
d
f
s
[
j
]
+
1
}
n
u
m
[
i
]
>
n
u
m
[
j
]
dfs[i]=\begin{cases} max\{dfs[i],dfs[j]+1\} & num[i]>num[j]\\ \end {cases}
dfs[i]={max{dfs[i],dfs[j]+1}num[i]>num[j]
所以我们就很容易写出这道题的答案:
完整注释代码如下:
#include <bits/stdc++.h>
using namespace std;
int main(){
int n,num[5005],ans[5005],ret; //用ans[i]表示dfs
cin>>n;
for(int i=1;i<=n;i++){
cin>>num[i];
ans[i]=1; //注意初始条件为1,因为每个数都至少有自身这个最大上身子序列
}
for(int i=1;i<=n;i++){
for(int j=1;j<=i;j++){
if(num[j]<num[i]){
ans[i]=max(ans[i],ans[j]+1);
}
}
}
for(int i=1;i<=n;i++){
ret=max(ret,ans[i]); //找出最大的结果就是答案
}
cout<<ret;
return 0;
}
3.最大子段和
题目描述
给出一个长度为 n 的序列 a,选出其中连续且非空的一段使得这段和最大。
输入格式
第一行是一个整数,表示序列的长度 n。
第二行有 n 个整数,第 i个整数表示序列的第 i 个数字 a_i。
输出格式
输出一行一个整数表示答案。
样例 #1
样例输入 #1
7
2 -4 3 -1 2 -4 3
样例输出 #1
4
提示
样例 1 解释
选取 [3, 5] 子段 {3, -1, 2},其和为 4。
数据规模与约定
- 对于 40% 的数据,保证 n <= 2 * 10^3。
- 对于 100% 的数据,保证 1 <= n <= 2 * 105,-104 <= a_i <= 10^4。
这道题我有一种好的想法:我们可以从头遍历到尾,用一个sum表示从第一个数加到第i个数的和,再定义一个max记录当前sum的最大值(sum初始化为0,max初始化为第一个数的值),那么我们就可以很快发现如果sum加完上一个数后如果小于0,那么我们就应该抛弃前面的所以数的值,再初始化sum为0(原因也很简单,因为前面的这些数的和是负数,只会让后面的数的和变小,那么当然要果断抛弃他们太可怜了qwq),当sum加完后依旧大于0,就代表前面的这些数之和还是可以让后面的这些数变大的,所以要继续向下加,每次加,我们都要用max记录sum的最大值,直到加到尾。那么这是的max也就是结果了.(其实也包含了动态规划的思想在里面)
完整注释代码如下:
#include <bits/stdc++.h>
using namespace std;
int main(){
int n,num[200005],sum;
cin>>n;
for(int i=0;i<n;i++){
cin>>num[i];
}
sum=0;
mx=num[0];
for(int i=0;i<n;i++){
sum+=num[i];
if(sum>=0){
mx=max(sum,mx); //如果sum>=0,那么就继续加下去,并记录最大值
}
else if(sum<0){
mx=max(sum,mx); //如果小于0就要果断抛弃qwq,并记录最大值
sum=0;
}
}
cout<<mx;
return 0;
}
4.LCS
题面翻译
题目描述:
给定一个字符串 s和一个字符串 t ,输出 s 和 t 的最长公共子序列。
输入格式:
两行,第一行输入 s ,第二行输入 t 。
输出格式:
输出 s 和 t的最长公共子序列。如果有多种答案,输出任何一个都可以。
说明/提示:
数据保证 s 和 t 仅含英文小写字母,并且 s 和 t 的长度小于等于3000。
题目描述
文字列 $ s $ および $ t $ が与えられます。 $ s $ の部分列かつ $ t $ の部分列であるような文字列のうち、最長のものをひとつ求めてください。
输入格式
入力は以下の形式で標準入力から与えられる。
$ s $ $ t $
输出格式
$ s $ の部分列かつ $ t $ の部分列であるような文字列のうち、最長のものをひとつ出力せよ。 答えが複数ある場合、どれを出力してもよい。
样例 #1
样例输入 #1
axyb
abyxb
样例输出 #1
axb
样例 #2
样例输入 #2
aa
xayaz
样例输出 #2
aa
样例 #3
样例输入 #3
a
z
样例输出 #3
样例 #4
样例输入 #4
abracadabra
avadakedavra
样例输出 #4
aaadara
提示
注釈
文字列 $ x $ の部分列とは、$ x $ から $ 0 $ 個以上の文字を取り除いた後、残りの文字を元の順序で連結して得られる文字列のことです。
制約
- $ s $ および $ t $ は英小文字からなる文字列である。
- $ 1\ \leq\ |s|,\ |t|\ \leq\ 3000 $
Sample Explanation 1
答えは axb
または ayb
です。 どちらを出力しても正解となります。
Sample Explanation 3
答えは `` (空文字列) です。
这道题我一开始是完全想不出什么思路,直到我灵感突然迸发~~(看了题解后),这了我就引用洛谷这位大佬的题解来解释一下(其实就是我看的人的题解)~~
证明的步骤直接查看题解的步骤就行了(我实在是语言贫瘠),所以我直接来看这个状态转移方程:
(这里用C[i][j]
表示在a的i个字符长度和b的j个字符长度中的最大LCS的长度)
C
[
i
,
j
]
=
{
0
若
i
=
0
或
j
=
0
C
[
i
−
1
,
j
−
1
]
+
1
若
i
,
j
>
0
且
x
i
=
y
j
max
(
C
[
i
,
j
−
1
]
,
C
[
i
−
1
,
j
]
)
若
i
,
j
>
0
且
x
i
≠
y
j
C[i,j]=\begin{cases} 0 & \text{若}i=0\text{或}j=0\\ C[i-1,j-1]+1 & \text{若}i,j>0\text{且}x_i=y_j\\ \max(C[i,j-1],C[i-1,j]) & \text{若}i,j>0\text{且}x_i\not=y_j \end{cases}
C[i,j]=⎩
⎨
⎧0C[i−1,j−1]+1max(C[i,j−1],C[i−1,j])若i=0或j=0若i,j>0且xi=yj若i,j>0且xi=yj
其实这个就是LCS的结论的公式表达
- 若x_n=y_mx**n=y**m,则x_n=y_m=z_kx**n=y**m=z**k且Z_{k-1}Z**k−1是X_{n-1}X**n−1和Y_{m-1}Y**m−1的LCS。
- 若x_n\not=y_mx**n\=y**m,当z_k\not=x_nz**k\=x**n时,ZZ是X_{n-1}X**n−1和YY的LCS。
- 若x_n\not=y_mx**n\=y**m,当z_k\not=y_mz**k\=y**m时,ZZ是XX和Y_{m-1}Y**m−1的LCS。
我们重在理解这个过程.
此外,题目要求我们输出这个子序列,所以在每一次做选择的时候,要记下来是从哪里得到这个答案的。可以用一个pre[i,j]pre[i,j]指向C[i,j]C[i,j]得到答案是是从哪里选择的,详细的我会在代码注释中解释。
完整注释代码如下:
#include <iostream>
using namespace std;
const int maxn = 3005;
int dp[maxn][maxn],pre[maxn][maxn]; //dp[i][j]用于记录结果
string A,B;
int solve(int a_now,int b_now){
if(a_now<0 || b_now<0){
return 0; //如果长度有一个为0,那么长度必为0
}
if(dp[a_now][b_now]){
return dp[a_now][b_now]; //如果已经算过就返回结果
}
if(A[a_now] == B[b_now]){
pre[a_now][b_now]=1; //1表示要找的字符就在(a_now,b_now)的位置上,并需要继续追踪(a_now-1,b_now-1)的位置
dp[a_now][b_now]=solve(a_now-1,b_now-1)+1;
return dp[a_now][b_now];
} else {
int x1 = solve(a_now-1,b_now);
int x2 = solve(a_now,b_now-1);
if(x1 > x2){
pre[a_now][b_now]=2; //2表示要追踪的字符在(a_now-1,b_now)的位置上
dp[a_now][b_now]=x1;
return x1;
}
else {
pre[a_now][b_now]=3; //3表示要追踪的字符在(a_now,b_now-1)的位置上
dp[a_now][b_now]=x2;
return x2;
}
}
}
void print(int x,int y){ //反向追踪字符
if(x<0 || y<0){ //如果到0就返回
return;
}
if(pre[x][y] == 1){
print(x-1,y-1);
cout<<A[x];
}
else if(pre[x][y] == 2){
print(x-1,y);
}
else {
print(x,y-1);
}
}
int main(){
cin>>A>>B;
int len1=A.length(),len2=B.length();
solve(len1,len2);
print(len1,len2);
return 0;
}
这道题太难了,不会做qwq