A - 上台阶2
小瓜想走上一个一共有n级的台阶,由于小瓜的腿长比较特殊,他一次只能向上走1级或者3级或者5级台阶。小瓜想知道他有多少种方法走上这n级台阶,你能帮帮他吗?
Input
一行一个整数n(n<=100000),表示一共有n级台阶。
Output
一行一个整数,表示小瓜上台阶的方案数*对100003取余*的结果。
Sample
Input | Output |
---|---|
3 | 2 |
与斐波那契数列类似,使用相同的方法推理即可
#include <iostream>
using namespace std;
const int f=100003;
int ans[100010]={1,1,1,2,3,5};//初始化第1,2,3,4,5台阶的方法数
int main()
{
int n,i;
cin>>n;
for (i=6;i<=n;i++) {
ans[i]=(ans[i-1]+ans[i-5]+ans[i-3])%f;
}
cout<<ans[n];
return 0;
}
B - 数字三角形
7 3 8 8 1 0 2 7 4 4 4 5 2 6 5 (图1)
图1给出了一个数字三角形。从三角形的顶部到底部有很多条不同的路径。对于每条路径,把路径上面的数加起来可以得到一个和,你的任务就是找到最大的和。
注意:路径上的每一步只能从一个数走到下一层上和它最近的左边的那个数或者右边的那个数。
Input
输入的是一行是一个整数N (1 < N <= 100),给出三角形的行数。下面的N行给出数字三角形。数字三角形上的数的范围都在0和100之间。
Output
输出最大的和。
Sample
Input | Output |
---|---|
5 7 3 8 8 1 0 2 7 4 4 4 5 2 6 5 | 30 |
动态规划题目,可以使用二维数组或一维数组解决,这里采用一维数组解决,从下向上一次找出最优解
#include <iostream>
#include <algorithm>
using namespace std;
const int f=110;
int a[f][f],dp[f];
int main()
{
int n,i,t;
cin>>n;
for (i=0;i<n;i++) {
for (t=0;t<=i;t++) {
cin>>a[i][t];
if (i==n-1) {
dp[t]=a[i][t];
}
}
}
for (i=n-2;i>=0;i--) {
for (t=0;t<=i;t++) {
dp[t]=max(a[i][t]+dp[t],a[i][t]+dp[t+1]);
}
}
cout<<dp[0]<<endl;
return 0;
}
C - 矩阵取数问题
一个N*N矩阵中有不同的正整数,经过这个格子,就能获得相应价值的奖励,从左上走到右下,只能向下向右走,求能够获得的最大价值。
例如:3 * 3的方格。
1 3 3
2 1 3
2 2 1
能够获得的最大价值为:11。
Input
第1行:N,N为矩阵的大小。(2 <= N <= 500) 第2 - N + 1行:每行N个数,中间用空格隔开,对应格子中奖励的价值。(1 <= N[i] <= 10000)
Output
输出能够获得的最大价值。
Sample
Input | Output |
---|---|
3 1 3 3 2 1 3 2 2 1 | 11 |
与上一题有些类似,但并不完全相同
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int f = 500;
int a[f][f];
ll dp[f][f];
int main() {
int n, i, t;
cin >> n;
for (i = 0; i < n; i++) {
for (t = 0; t < n; t++) {
cin >> a[i][t];
}
}
for (i = 0; i < n; i++) {
for (t = 0; t < n; t++) {
if (i == 0 && t == 0) {//起始位置
dp[i][t] = a[i][t];
} else if (i == 0) {//只能向右走
dp[i][t] = dp[i][t - 1] + a[i][t];
} else if (t == 0) {//只能向下走
dp[i][t] = dp[i - 1][t] + a[i][t];
} else {
dp[i][t] = max(dp[i][t - 1], dp[i - 1][t]) + a[i][t];
}
}
}
cout << dp[n - 1][n - 1] << endl;
return 0;
}
D - 背包问题
在N件物品取出若干件放在容量为W的背包里,每件物品的体积为W1,W2……Wn(Wi为整数),与之相对应的价值为P1,P2……Pn(Pi为整数)。求背包能够容纳的最大价值。
其中1 <= N <= 100,1 <= W <= 10000,每个物品1 <= Wi, Pi <= 10000。
Input
第1行输入两个整数N和W; 第2 ~ N+1行,每行两个整数Wi和Pi,分别表示每个物品的体积和价值。
Output
输出可以容纳的最大价值。
Sample
Input | Output |
---|---|
3 6 2 5 3 8 4 9 | 14 |
经典的01背包问题,这里采用一维数组解决问题
#include <iostream>
#include <algorithm>
using namespace std;
const int f=100010;
int a[f],b[f],dp[f];
int main()
{
int n,i,t,m;
cin>>n>>m;
for (i=0;i<n;i++) {
cin>>a[i]>>b[i];
}
for (i=0;i<n;i++) {//遍历物品
for (t=m;t>=a[i];t--) {//遍历背包
dp[t]=max(dp[t],dp[t-a[i]]+b[i]);//dp[t]不放置当前物品;dp[t-a[i]]+b[i]为放置当前物品
}
}
cout<<dp[m]<<endl;
return 0;
}
E - 完全背包
有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的体积是v[i],价值是c[i]。
现在请你选取一些物品装入背包,使这些物品的体积总和不超过背包容量,且价值总和最大。
其中1<=N<=100,1<=V<=50000,1<=v[i],c[i]<=10000。
Input
第一行输入两个数N,V,分别表示物品种类数和背包容积; 之后N行,每行两个数v[i],c[i],分别表示第i种物品的体积和价值;
Output
输出一个数,表示最大的价值
Sample
Input | Output |
---|---|
2 11 2 3 6 14 | 20 |
与01背包不同的是物品的数量不同,物品有无数个
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int f=50010;
int dp[f],a[110],b[110];
int main()
{
int n,i,m;
cin>>n>>m;
for (i=0;i<n;i++) {
cin>>a[i]>>b[i];
}
for (i=0;i<n;i++) {
for (int t=a[i];t<=m;t++) {
dp[t]=max(dp[t],dp[t-a[i]]+b[i]);
}
}
cout<<dp[m]<<endl;
return 0;
}
F - 背包问题 V2
有N种物品,每种物品的数量为C1,C2......Cn。从中任选若干件放在容量为W的背包里,每种物品的体积为W1,W2......Wn(Wi为整数),与之相对应的价值为P1,P2......Pn(Pi为整数)。求背包能够容纳的最大价值。
其中1 <= N <= 100,1 <= W <= 50000,1 <= Wi, Pi <= 10000, 1 <= Ci <= 200。
Input
第1行,2个整数,N和W中间用空格隔开。N为物品的种类,W为背包的容量。 第2 ~ N+1行,每行3个整数,Wi,Pi和Ci分别是物品体积、价值和数量。
Output
输出可以容纳的最大价值。
Sample
Input | Output |
---|---|
3 6 2 2 5 3 3 8 1 4 1 | 9 |
介于01背包与完全背包之间的问题,可以将其转化为01背包,及将物品分散开
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int f=50010;
const int g=20010;
int dp[f],a[g],b[g];
int main()
{
int n,i,m,j=0,x,y,z;
cin>>n>>m;
for (i=0;i<n;i++) {
cin>>x>>y>>z;
while (z--) {//分散物品,变成01背包
a[j]=x;
b[j]=y;
j++;
}
}
for (i=0;i<j;i++) {
for (int t=m;t>=a[i];t--) {
dp[t]=max(dp[t],dp[t-a[i]]+b[i]);
}
}
cout<<dp[m]<<endl;
return 0;
}
G - 最长上升子序列
一个数的序列bi,当b1 < b2 < ... < bS的时候,我们称这个序列是上升的。对于给定的一个序列(a1, a2, ..., aN),我们可以得到一些上升的子序列(ai1, ai2, ..., aiK),这里1 <= i1 < i2 < ... < iK <= N。比如,对于序列(1, 7, 3, 5, 9, 4, 8),有它的一些上升子序列,如(1, 7), (3, 4, 8)等等。这些子序列中最长的长度是4,比如子序列(1, 3, 5, 8).
你的任务,就是对于给定的序列,求出最长上升子序列的长度。
Input
输入的第一行是序列的长度N (1 <= N <= 1000)。第二行给出序列中的N个整数,这些整数的取值范围都在0到10000。
Output
最长上升子序列的长度。
Sample
Input | Output |
---|---|
7 1 7 3 5 9 4 8 | 4 |
这里采用的是将每一数作为子串的结尾,然后考虑其是否能和前面的数字组合成递增子串
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int f=1010;
int a[f],dp[f];
int main()
{
int n,i,ans=1;
cin>>n;
for (i=0;i<n;i++) {
cin>>a[i];
dp[i]=1;
}
for (i=1;i<n;i++) {
for (int j=0;j<i;j++) {
if (a[i]>a[j]) {
dp[i]=max(dp[i],dp[j]+1);
}
ans=max(ans,dp[i]);
}
}
cout<<ans<<endl;
return 0;
}
I - 石子合并
将 nn 堆石子绕圆形操场排放,现要将石子有序地合并成一堆。规定每次只能选相邻的两堆合并成新的一堆,并将新的一堆的石子数记做该次合并的得分。
请编写一个程序,读入堆数 n 及每堆的石子数,并进行如下计算:
- 选择一种合并石子的方案,使得做 n-1 次合并得分总和最大。
- 选择一种合并石子的方案,使得做 n-1 次合并得分总和最小。
输入格式
输入第一行一个整数 n,表示有 n 堆石子。
第二行 n 个整数,表示每堆石子的数量。
输出格式
输出共两行:
第一行为合并得分总和最小值,
第二行为合并得分总和最大值。
区间dp,可以看下视频加以理解区间dp
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long ll;
const int f=410,INF=0x3f3f3f3f;
int a[f],sum[f],mi[f][f],ma[f][f];
int main()
{
int i,n,t;
cin>>n;
for (i=1;i<=n;i++) {
cin>>a[i];
a[n+i]=a[i];
}
for (i=1;i<=2*n;i++) {
sum[i]=sum[i-1]+a[i];
}
memset(mi,0x3f,sizeof(mi));
memset(ma,-0x3f,sizeof(ma));
for (i=1;i<=n;i++) {
for (t=1;t+i-1<=2*n;t++) {
int r=t+i-1;
if (r==t) {
mi[t][r]=0;
ma[t][r]=0;
} else {
for (int j=t;j<=r;j++) {
mi[t][r]=min(mi[t][r],mi[t][j]+mi[j+1][r]+sum[r]-sum[t-1]);
ma[t][r]=max(ma[t][r],ma[t][j]+ma[j+1][r]+sum[r]-sum[t-1]);
}
}
}
}
int mx=-INF;
int mn=INF;
for (i=1;i<=n;i++) {
mx=max(mx,ma[i][i+n-1]);
mn=min(mn,mi[i][i+n-1]);
}
cout<<mn<<endl<<mx;
return 0;
}
J - 循环数组最大子段和
N个整数组成的循环序列a[1],a[2],a[3],…,a[n],求该序列如a[i]+a[i+1]+…+a[j]的连续的子段和的最大值(循环序列是指n个数围成一个圈,因此需要考虑a[n-1],a[n],a[1],a[2]这样的序列)。当所给的整数均为负数时和为0。
例如:-2,11,-4,13,-5,-2,和最大的子段为:11,-4,13。和为20。
Input
第1行:整数序列的长度N(2 <= N <= 50000) 第2 - N+1行:N个整数 (-10^9 <= S[i] <= 10^9)
Output
输出循环数组的最大子段和。
Sample
Input | Output |
---|---|
6 -2 11 -4 13 -5 -2 | 20 |
由于是循环数组那么也就导致了两种情况:
1.中间的子串和最大
2.首位子串和最大
那么我们就需要分情况讨论,对于第二种情况的处理首位的字串和最大也就说明了中间的字串和为负数,那么计算方式就可以是,数组的和减去中间的和就是两边的和
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn=5e5+10;
int a[maxn];
ll maxnum(int n) {
int i;
ll max=0;
ll sum=0;
for (i=0;i<n;i++) {
sum+=a[i];
if (sum>max) max=sum;
if (sum<0) sum=0;
}
return max;
}
int main()
{
int n,i;
cin>>n;
ll sum=0;
for (i=0;i<n;i++) {
cin>>a[i];
sum+=a[i];
}
ll sum1=maxnum(n);//中间字串和计算
for (i=0;i<n;i++) {
a[i]*=-1;
}
ll sum2=maxnum(n);
sum2+=sum;//首位子串和计算
ll ans=max(sum1,sum2);
cout<<ans<<endl;
return 0;
}
L - 滑雪
Michael喜欢滑雪百这并不奇怪, 因为滑雪的确很刺激。可是为了获得速度,滑的区域必须向下倾斜,而且当你滑到坡底,你不得不再次走上坡或者等待升降机来载你。Michael想知道载一个区域中最长的滑坡。区域由一个二维数组给出。数组的每个数字代表点的高度。下面是一个例子
1 2 3 4 5 16 17 18 19 6 15 24 25 20 7 14 23 22 21 8 13 12 11 10 9
一个人可以从某个点滑向上下左右相邻四个点之一,当且仅当高度减小。在上面的例子中,一条可滑行的滑坡为24-17-16-1。当然25-24-23-...-3-2-1更长。事实上,这是最长的一条。
Input
输入的第一行表示区域的行数R和列数C(1 <= R,C <= 100)。下面是R行,每行有C个整数,代表高度h,0<=h<=10000。
Output
输出最长区域的长度。
Sample
Input | Output |
---|---|
5 5 1 2 3 4 5 16 17 18 19 6 15 24 25 20 7 14 23 22 21 8 13 12 11 10 9 | 25 |
这里采用的方法是从小到大遍历高度
1.“我为人人”型
顾名思义,就是由自己的情况推出别人的
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef struct data{
int x;
int y;
int date;
}HX;
const int f=110;
int dp[f][f],a[f][f];
int b[4][2]={{1,0},{-1,0},{0,1},{0,-1}};
bool cmp(HX a,HX b) {
return a.date<b.date;
}
int main()
{
int m,n,i,t,j=0;
cin>>m>>n;
for (i=0;i<m;i++) {
for (t=0;t<n;t++) {
cin>>a[i][t];
dp[i][t]=1;
}
}
HX c[10010];
for (i=0;i<m;i++) {
for (t=0;t<n;t++) {
c[++j].date=a[i][t];
c[j].x=i;
c[j].y=t;
}
}
sort(c+1,c+j+1,cmp);
int ans=1;
for (i=1;i<=j;i++) {
for (t=0;t<4;t++) {
int x=c[i].x;
int y=c[i].y;
int tx=x+b[t][0];
int ty=y+b[t][1];
if (tx>=0&&ty>=0&&tx<m&&ty<n) {
if (a[tx][ty]>a[x][y]) {
dp[tx][ty]=max(dp[tx][ty],dp[x][y]+1);
ans=max(ans,dp[tx][ty]);
}
}
}
}
cout<<ans<<endl;
return 0;
}
2.“人人为我”型
就是根据别人的情况推出自己的情况
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef struct date {
int x;
int y;
int date;
}HX;
int dp[110][110],a[110][110];
int b[4][2]={{1,0},{-1,0},{0,1},{0,-1}};
bool cmp (HX a,HX b) {
return a.date<b.date;
}
int main()
{
int m,n,i,t;
cin>>m>>n;
HX s[10010];
for (i=0;i<m;i++) {
for (t=0;t<n;t++) {
cin>>a[i][t];
}
}
int k=0;
for (i=0;i<m;i++) {
for (t=0;t<n;t++) {
s[++k].date=a[i][t];
s[k].x=i;
s[k].y=t;
dp[i][t]=1;
}
}
sort(s+1,s+k+1,cmp);
int ans=1;
for (i=1;i<=k;i++) {
for (t=0;t<4;t++) {
int x=s[i].x;
int y=s[i].y;
int tx=x+b[t][0];
int ty=y+b[t][1];
if (tx>=0&&ty>=0&&tx<m&&ty<n) {
if (a[x][y]>a[tx][ty]) {
dp[x][y]=max(dp[x][y],dp[tx][ty]+1);
ans=max(ans,dp[x][y]);
}
}
}
}
cout<<ans<<endl;
return 0;
}