这周主要学习dp(动态规划)
1、
题目:洛谷B3637 最长上升子序列
这是一个简单的动规板子题。给出一个由n(n≤5000) 个不超过10^6的正整数组成的序列。请输出这个序列的最长上升子序列的长度。最长上升子序列是指,从原序列中按顺序取出一些数字排在一起,这些数字是逐渐增大的。
输入
第一行,一个整数 n,表示序列长度。
第二行有 n 个整数,表示这个序列。
输出
一个整数表示答案。
思路:
最长上升子序列是常见的dp问题,对于这种题,先用一个数组(num[ ])储存输入的数据,然后用另一个数组(tm[ ])(初始化为1)储存当前最长上升子序列的长度,然后从第一项开始遍历,遍历到第m项,如果下一项的数大于0~m项中的一项(x),并且如果tm[x-1]+1大于下一项的tm[ ],就让下一项的tm[ ]等于tm[x-1]+1,最后排序,最大的tm[ ]就是最长上升子序列的长度
# include <bits/stdc++.h>
using namespace std;
int main()
{
int n,ans=0;
scanf("%d",&n);
int num[n],tm[n];
for(int d=0;d<n;d++) tm[d]=1;
for(int b=0;b<n;b++)
{
scanf("%d",&num[b]);
}
for(int a=0;a<n;a++)
{
for(int c=0;c<a;c++)
{
if(num[a]>num[c]&&tm[c]>=tm[a]-1) tm[a]=tm[c]+1;
}
}
sort(tm,tm+n);
printf("%d",tm[n-1]);
return 0;
}
2、
题目:洛谷P1115 最大子段和
给出一个长度为 n 的序列 a,选出其中连续且非空的一段使得这段和最大。
输入
第一行是一个整数,表示序列的长度 n。
第二行有 n 个整数,第 i 个整数表示序列的第 i 个数字 ai。
输出
输出一行一个整数表示答案。
思路:
先用数组num[ ]储存输入的n个数据,然后再开辟一个数组tm[ ](用于储存最大值)并初始化为当前数据( num[0]=tm[0] num[1]=tm[1]……),求每一个(遍历)数据(x)的tm[ ]时,都比一下x-1的tm是大于0还是小于0,如果大于0,就加到x的tm上,否则不加,最后再排序,输出最大的tm即为最大值
# include <bits/stdc++.h>
using namespace std;
int main()
{
int n,a,b;
scanf("%d",&n);
int num[n],tm[n];
for(int c=0;c<n;c++)
{
scanf("%d",&num[c]);
tm[c]=num[c];
}
for(int d=1;d<n;d++)
{
if(tm[d-1]>0) tm[d]=num[d]+tm[d-1];
printf("tm[%d]=%d\n",d,tm[d]);
}
sort(tm,tm+n);
printf("%d",tm[n-1]);
return 0;
}
3、
题目:洛谷P8707 [蓝桥杯 2020 省 AB1] 走方格
在平面上有一些二维的点阵。这些点的编号就像二维数组的编号一样,从上到下依次为第 1 至第 n 行,从左到右依次为第 1 至第 m 列,每一个点可以用行号和列号来表示现在有个人站在第 1 行第 1 列,要走到第 n 行第 m 列。只能向右或者向下走。注意,如果行号和列数都是偶数,不能走入这一格中。问有多少种方案。输入:一行包含两个整数 n,m。输出:一个整数,表示答案。
思路:
先用n、m储存行列数,开辟二维数组num[ ][ ],如果要到达第i+1行第j+1列的点num[i][j],要么是从num[i-1][j]向下一步走过来的,要么是从num[i][j-1]向右一步走过来的,所以num[i][j]=num[i-1][j]+num[i][j-1],同时由于第一列的点只能向下一步到达,第一行的一步只能向右一步到达,所以第一行和第一列的num为1,并且偶数不能走入,所以num[奇][奇](数组从0开始)的num为0,用两个for循环嵌套,遍历每个点如果num[奇][奇]则为0,否则num[i][j]=num[i-1][j]+num[i][j-1],最后输出num[n-1][m-1]即可。
# include <bits/stdc++.h>
using namespace std;
int main()
{
int m,n;
scanf("%d %d",&n,&m);
int num[n][m]={0};
for(int a=1;a<n;a++)
{
num[a][0]=1;
}
for(int b=1;b<m;b++)
{
num[0][b]=1;
}
for(int c=1;c<n;c++)
{
for(int d=1;d<m;d++)
{
if(c==n-1&&d==m-1)
{
num[c][d]=num[c-1][d]+num[c][d-1];
break;
}
if((c+1)%2==0&&(d+1)%2==0)
num[c][d]=0;
else
num[c][d]=num[c-1][d]+num[c][d-1];
}
}
printf("%d",num[n-1][m-1]);
return 0;
}
4、
题目:洛谷P1216 [USACO1.5] [IOI1994]数字三角形 Number Triangles
观察下面的数字金字塔。写一个程序来查找从最高点到底部任意处结束的路径,使路径经过数字的和最大。每一步可以走到左下方的点也可以到达右下方的点。
在上面的样例中,从 7→3→8→7→57→3→8→7→5 的路径产生了最大权值。
输入:第一个行一个正整数 r ,表示行的数目。后面每行为这个数字金字塔特定行包含的整数。
输出:单独的一行,包含那个可能得到的最大的和。
思路:
数塔是dp中较为典型的问题,假设一共有n行,先用二维数组num储存数据,开辟一个tm二维数组(用于储存最大值),让tm数组中的每一项等于num数组中的每一项,然后从第n-1行(倒数第二行)的第一个数开始,如果第n行的第一个数大于第二个数,那么第n-1行的第一个数的tm就等于它本身加上第n行的第一个数,否则等于它本身加上第n行第二个数;如果第n行的第二个数大于第三个数,那么第n-1行的第二个数的tm就等于它本身加上第n行的第二个数,否则等于它本身加上第n行第三个数…………以此类推
# include <bits/stdc++.h>
using namespace std;
int main()
{
int r;
scanf("%d",&r);
int num[r][r];
int tm[r][r];
for(int a=0;a<r;a++)
{
for(int b=0;b<a+1;b++)
{
scanf("%d",&num[a][b]);
tm[a][b]=num[a][b];
}
}
for(int c=r-2;c>=0;c--)
{
for(int d=0;d<=c+2;d++)
{
if(tm[c+1][d]>tm[c+1][d+1]) tm[c][d]+=tm[c+1][d];
else tm[c][d]+=tm[c+1][d+1];
}
}
printf("%d",tm[0][0]);
return 0;
}
5、
题目:P1020 [NOIP1999 普及组] 导弹拦截
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。输入导弹依次飞来的高度,计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
输入:一行,若干个整数,中间由空格隔
开。
输出:两行,每行一个整数,第一个数字表示这套系统最多能拦截多少导弹,第二个数字表示如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
提示:对于前 50% 数据(NOIP 原题数据),满足导弹的个数不超过 10^4 个。该部分数据总分共 100 分。可使用O(n^2) 做法通过。
对于后 50% 的数据,满足导弹的个数不超过 10^5 个。该部分数据总分也为100 分。请使用 O(nlogn) 做法通过。
思路:很明显根据题目可知,第一个数字是最长递增子序列的长度,第二个数字是最长递减子序列的长度
100分代码(复杂度为o(n^2))
include <bits/stdc++.h>
using namespace std;
int main()
{
int num[100010]={0},a=0,tm[100010],am[100010];
while(scanf("%d",&num[a])==1)
{
tm[a]=1;
am[a]=1;
a++;
}
int n=a;
for(int b=1;b<n;b++)
{
for(int c=0;c<b;c++)
{
if(num[c]>=num[b]&&tm[c]+1>tm[b]) tm[b]=tm[c]+1;
}
}
sort(tm,tm+n);
printf("%d\n",tm[n-1]);
for(int b=1;b<n;b++)
{
for(int c=0;c<b;c++)
{
if(num[c]<num[b]&&am[c]+1>am[b]) am[b]=am[c]+1;
}
}
sort(am,am+n);
printf("%d",am[n-1]);
return 0;
}
AC代码思路:
与o(n^2)代码思路类似,只不过是求最长上升子序列和最长下降子序列的长度时使用的方法不同,为了使复杂度变为o(nlogn)可以使用二分查找,同时使用到函数upper_bound和函数lower_bounder,前者的作用是查找第一个比目标元素大的元素的位置,后者的目的是查找第一个大于等于目标元素的元素的位置。
先令dp【】数组全为无穷大的数(全为0也可以,不过是另一种思路)假设输入的数为
389 207 155 300 299 170 158 65,储存为数组num[ ],然后让数组turn[ ]储存逆序的数组num[ ],
从0开始遍历turn[ ]数组,从dp数组中找到第一个大于turn[0]的位置(由于dp数组无穷大,所以位置是0),然后让dp【0】=turn[0]……以此类推,最终得到dp数组为
65 155(原来是207) 170 207(原来是209) 300 389 无穷大 无穷大 ……所以再用lower_bound函数在dp数组中查找第一个无穷大的位置,找到位置为6,所以数组num的最长下降子序列长度为6,同理可得最长上升子序列的长度
AC代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
int main()
{
const int INF=0x3f3f3f3f;
int num[100010]={0},n=0,dp[100010],turn[100010];
while(scanf("%d",&num[n])==1)
{
n++;
}
fill(dp,dp+n,INF);
int k=0;
for(int a=n-1;a>=0;a--)
{
turn[a]=num[k];
k++;
}
for(int j = 0;j<n;j++)
{
int pos = upper_bound(dp,dp+n,turn[j]) - dp;
dp[pos] = turn[j];
}
cout<<lower_bound(dp,dp+n,INF)-dp<<endl;
fill(dp,dp+n,INF);
for(int j = 0;j<n;j++)
{
int pos = lower_bound(dp,dp+n,num[j]) - dp;
dp[pos] = num[j];
}
cout<<lower_bound(dp,dp+n,INF)-dp<<endl;
return 0;
}