P1048 [NOIP2005 普及组] 采药
题目描述
辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”
如果你是辰辰,你能完成这个任务吗?
输入格式
第一行有 22 个整数 TT(1 \le T \le 10001≤T≤1000)和 MM(1 \le M \le 1001≤M≤100),用一个空格隔开,TT 代表总共能够用来采药的时间,MM 代表山洞里的草药的数目。
接下来的 MM 行每行包括两个在 11 到 100100 之间(包括 11 和 100100)的整数,分别表示采摘某株草药的时间和这株草药的价值。
输出格式
输出在规定的时间内可以采到的草药的最大总价值。
输入输出样例
输入 #1
70 3 71 100 69 1 1 2
输出 #1
3
说明/提示
【数据范围】
- 对于 30\%30% 的数据,M \le 10M≤10;
- 对于全部的数据,M \le 100M≤100。
【题目来源】
NOIP 2005 普及组第三题
题解:
思路就是先建立重量数组和价值数组,将题目中的数据录进去,然后建立最优价值数组二维数组,行为每个药材的序号,列为重量,大小为从1到最大重量,然后通过比较将价值更大的药材存进去,
比如容量是70,第一个药材重量71,价值为100,显然重量从1-70都装不下这个药材,所以最优价值数组zy[1][1]-zy[1][70]中录入的最优价值都是0;
第二个药材重量69,价值为1,通过判断,当重量为70和69时,先不判断是不是最优,起码这个药材有被放进去的机会,然后重量为1-68时放不进去,所以直接继承上一个数组的值,因为上一个数组储存的值是截止上一个药材而言的最优解;当重量为70和69时,通过吗max函数比较:1.如果减去这个药材的重量,那么截止上一个药材对应的最优解加上这个药材的价值;2.当前重量对应的截止上一个函数的最优解;两值的大小,选取最大值存储;易得zy[2][70]=1,zy[2][69]=1;
第三个药材重量是1,价值是2,zy[3][70]中方案1对应的值是3,方案2是2,所以储存为三;
至此到答案,输出zy[3][70],结果为3;
题解如下:
#include <iostream>
#include <cmath>
using namespace std;
int zl[105], val[105];
int jzjl[105][1005];
int main()
{
int t, m;
cin >> t >> m;
for (int i = 1; i <= 0; i++)
{
cin>> zl[i]>>val[i];
}
for (int i = 1; i <= m; i++) {
for (int j = t; j >= t; j--)
{
if (j >= zl[i])
{
jzjl[i][j] = max(jzjl[i - 1][j - zl[i]] + val[i], jzjl[i - 1][j]);
//cout<<"存进去" << i << " " << j << " " << jzjl[i][j] << endl;
}
else
{
jzjl[i][j] = jzjl[i - 1][j];
//cout<<"存不进" << i << " " << j << " " << jzjl[i][j] << endl;
}
}
}
cout<< jzjl[m][t];
system("pause");
return 0;
}
B3637 最长上升子序列
题目描述
这是一个简单的动规板子题。
给出一个由 n(n\le 5000)n(n≤5000) 个不超过 10^6106 的正整数组成的序列。请输出这个序列的最长上升子序列的长度。
最长上升子序列是指,从原序列中按顺序取出一些数字排在一起,这些数字是逐渐增大的。
输入格式
第一行,一个整数 nn,表示序列长度。
第二行有 nn 个整数,表示这个序列。
输出格式
一个整数表示答案。
输入输出样例
输入 #1
6 1 2 4 1 3 4
输出 #1
4
说明/提示
分别取出 11、22、33、44 即可。
题解:
从外围从第一个数开始遍历,内围从1遍历到外围数字-1,相当于给每个数字找低于自己的数字,然后叠高高,每次遍历的时候,作为外围的数字都可以选择自己对接上去后最长的队列,因为是一个一个排,所以一定是从小到大
像例题1,2,4,1,3,4排列起来顺序大概就是:
1 1 1 1 1 1
2 2 2 2
4 3 3
4
emmm,有点抽象,不过大概就是这样,从左向右就是顺序,
第一个数字1找不到人,最长队列就是自己,
第二个 数字2找到1,最长队列是1,2
第三个数字4找到1,2,最长队列是1,2,4
以此类推,不过我们不用记录队列是怎么排 的,只要记住他有几个数就够了,所以一个一维数组用来记录数据即可,最后找到最长的,输出,结束
#include<iostream>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
int a[5005];
int main()
{
int n;
cin >> n;
vector<int>dp(n+1,1);
dp[0] = 0;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i++)
{
dp[i] = 1;
//cout << "i=" << i << endl;
for (int j = 1; j <= i - 1; j++)
{
//cout << "j=" << j << endl;
if (a[j] < a[i]) {
dp[i] = max<int>(dp[i], dp[j] + 1);
//cout<<"dp["<<i<<"]=" << dp[i] << endl;
}
}
}
sort(dp.begin() + 1, dp.end());
cout << dp.back();
system("pause");
return 0;
}
P1115 最大子段和
题目描述
给出一个长度为 nn 的序列 aa,选出其中连续且非空的一段使得这段和最大。
输入格式
第一行是一个整数,表示序列的长度 nn。
第二行有 nn 个整数,第 ii 个整数表示序列的第 ii 个数字 a_iai。
输出格式
输出一行一个整数表示答案。
输入输出样例
输入 #1
7 2 -4 3 -1 2 -4 3
输出 #1
4
说明/提示
样例 1 解释
选取 [3, 5][3,5] 子段 \{3, -1, 2\}{3,−1,2},其和为 44。
数据规模与约定
- 对于 40\%40% 的数据,保证 n \leq 2 \times 10^3n≤2×103。
- 对于 100\%100% 的数据,保证 1 \leq n \leq 2 \times 10^51≤n≤2×105,-10^4 \leq a_i \leq 10^4−104≤ai≤104。
题解:
这个题不难,思路还是比较清晰,不过开始想的太简单了。
先说下最开始时间超限的解法,直接两个for循环相套,外层从0到n-1遍历每个数,内层选择从外层记录的数开始最大是几,数组n用来记录内层选择的数据
明了一些来说就是:
m[e]:储存从这个数开始最大能加到几
int jishu:内层每加一个数,jishu就+=m[i]
然后不断判断m[e]和jishu谁大,谁大存谁,因为必须是连续的,所以只需要jishu一个变量就可以实现整个过程
当然,因为复杂程度是O(n2),所以五个案例三个报了超时,很悲伤。
痛定思痛优化了一下方法,既然jishu每次都要从外围数字一路加到最大,那是不是可以重复利用其中的这个过程呢。
然后就有了新思路:
反着来:
开始定义最优变量为int zy =0;因为最后一个数后面再没数了,加不了别人了
然后加一个判定,如果zy>0,那么统计数组h[e]=m[e]+zy;
否则h[e]=m[e];
很好理解,就是如果加上后面那个会变大那就加,变大不了那就拉倒,不管他了
然后zy更新为刚刚确定的h[e],此时zy就代表了从当前e开始向后加最大能加到多少,
然后外围往前推进,把每个数都遍历一遍,最后选出最大值输出,结束
然后就成了,思路没太大变化,但是省去了内层的for循环,复杂度变成了O[n],就不会超时了
#include<iostream>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
int main()
{
int n;
cin >> n;
vector<int>m(n, 0);
vector<int>h(n, 0);
for (int e = 0; e < n; e++) {
cin >> m[e];
}
int zy = 0;
for (int e = n-1; e>=0; e--) {
if (zy > 0) {
h[e] = m[e] + zy;
}
else {
h[e] = m[e];
}
zy = h[e];
}
sort(h.begin(), h.end());
cout << h.back();
system("pause");
return 0;
}
LCS
题意翻译
题目描述:
给定一个字符串 ss 和一个字符串 tt ,输出 ss 和 tt 的最长公共子序列。
输入格式:
两行,第一行输入 ss ,第二行输入 tt 。
输出格式:
输出 ss 和 tt 的最长公共子序列。如果有多种答案,输出任何一个都可以。
说明/提示:
数据保证 ss 和 tt 仅含英文小写字母,并且 ss 和 tt 的长度小于等于3000。
输入输出样例
输入 #1
axyb abyxb
输出 #1
axb
输入 #2
aa xayaz
输出 #2
aa
输入 #3
a z
输出 #3
输入 #4
abracadabra avadakedavra
输出 #4
aaadara
题解:
先照dp模板,记录字符串a里面每个字符对应字符串b里面的每个字符能有多少个,算出怎么样会让公共子序列最长
假设字符串a是
abcde
字符串b是
eabcd
那二维数组的赋值过程就是
a不等于e ,【1】【1】为0;
a等于a,【1】【2】为1;
a不等于b,【1】【3】为0;
a不等于c,【1】【4】为0;
a不等于d,【1】【5】为0;
下一步,开始字符串a的第二个字符b
b不等于e,【2】【1】为0;
b不等于a,但是因为【1】【2】是1,此时对于字符串“ab”来说可以继承a的结果,【2】【2】为1;
b等于b,【2】【3】为1;
b不等于c,【2】【4】为0;
b不等于d,【2】【5】为0;
以此类推,当不等于时就找二维数组左边和上边最大的一个录入
然后再从后往前判断最长长度的字符串是怎么加上来的 ,最后输出
#include<iostream>
using namespace std;
int dp[3005][3005];
char a[3005], b[3005], c[3005];
int main()
{
string a;
string b;
cin >> a >> b;
a.insert(a.begin(), 0);
b.insert(b.begin(), 0);
int n = a.size()-1, m =b.size()-1;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
if (a[i] == b[j])
{
dp[i][j] = dp[i - 1][j - 1] + 1;
}
else
{
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
int i = n, j = m;
while (dp[i][j] > 0)
{
if (a[i] == b[j])
{
c[dp[i][j]] = a[i];
i--;
j--;
}
else
{
if (dp[i][j] == dp[i - 1][j]) i--;
else j--;
}
}
printf("%s", c + 1);
system("pause");
return 0;
}