本文章主要记录第四周做题的思路。如有错误,请大佬斧正。若大佬有更好的思路,也请指教。
本周涉及动态规划、筏、递推、二分、贪心。
一、B3637 最长上升子序列
题目:这是一个简单的动规板子题。给出一个由 n(n≤5000) 个不超过 106 的正整数组成的序列。请输出这个序列的最长上升子序列的长度。最长上升子序列是指,从原序列中按顺序取出一些数字排在一起,这些数字是逐渐增大的。
思路:本题我用了一个相当low的做法,在下面第五题有更好的方式(筏)。在这求长度为n的数列时,会从第二个数开始,将其与之前的每一个数比较大小,如果大于,那么前面那个数的dp[]值加一。每个数的dp[]默认为1,第n个数的dp[]值是前面小于它的最大的dp[]值加一。
代码:
#include<bits/stdc++.h>
using namespace std;
struct line {
int s,l;
};
bool com(line a,line b)
{
return a.l>b.l;
}
int main()
{
int n;
cin>>n;
line f[n];
for(int i=0;i<n;i++)
{
cin>>f[i].s;
}
f[0].l=1;
for(int i=1;i<n;i++)
{
f[i].l=1;
for(int y=0;y<i;y++)
{
if(f[i].s>f[y].s&&f[i].l<=f[y].l)
{
f[i].l=f[y].l+1;
}
}
}
sort(f,f+n,com);
cout<<f[0].l;
}
二、P1115 最大子段和
题目:给出一个长度为 n 的序列 a,选出其中连续且非空的一段使得这段和最大。
思路:要求n个数的最大子段和,假设n-1个数的最大值已知,那么此时只需要先求出以第n个数为右端点的最大字段和,再将之与n-1的最大值相比,大值即使n个数的最大子段和。
代码:
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n,tem,maxs,lin;
cin>>n;
int f[n];
for(int i=0;i<n;i++)
{
cin>>f[i];
}
maxs=tem=f[0];
for(int y=1;y<n;y++)
{
tem=max(f[y],tem+f[y]);
maxs=max(tem,maxs);
}
cout<<maxs;
}
三、P8707 [蓝桥杯 2020 省 AB1] 走方格
题目:在平面上有一些二维的点阵。这些点的编号就像二维数组的编号一样,从上到下依次为第 11 至第 n 行,从左到右依次为第 11 至第 m 列,每一个点可以用行号和列号来表示。现在有个人站在第 11 行第 11 列,要走到第n 行第 m 列。只能向右或者向下走。注意,如果行号和列数都是偶数,不能走入这一格中。问有多少种方案。
思路:做个二维数组,从左上角开始赋值,值代表从起点到此的方案数。不能走的格的值赋为0,这样从上到下、从左向右地逐一赋值,不就是动态规划了吗?
代码:
#include <bits/stdc++.h>
using namespace std;
int main()
{
int j,k,n,m;
cin>>j>>k;
n=max(j,k);
m=min(j,k);
int f[n+1][n+1];
memset(f,0,sizeof(f));
for(int i=1;i<=n;i++)
{
f[1][i]=1;
f[i][1]=1;
}
for(int i=2;i<=n;i++)
for(int y=2;y<=m;y++)
{
if(i%2==0&&y%2==0) f[i][y]=0;
else f[i][y]=f[i-1][y]+f[i][y-1];
}
cout<<f[n][m];
}
四、P1216 [USACO1.5] [IOI1994]数字三角形 Number Triangles
题目:观察下面的数字金字塔。写一个程序来查找从最高点到底部任意处结束的路径,使路径经过数字的和最大。每一步可以走到左下方的点也可以到达右下方的点。
在上面的样例中,从 7→3→8→7→57→3→8→7→5 的路径产生了最大权值。
思路:十分经典的动态规划问题。从最下面的小三角看,在4和5之间选择大值与上一行的2相加,同理,最下面一行的5和2选出大值与上一行的7相加,当最下面一行算完时,倒数第二行的值已将被重置了,此时再重复对最后一行做的操作,由下到上,直至第一行的数也被重置,得到了最大权数。
代码:
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n;
cin>>n;
int f[n][n],dp[n];
for(int i=0;i<n;i++)
{
for(int a=0;a<=i;a++)
cin>>f[i][a];
}
for(int i=0;i<n;i++)
{
dp[i]=f[n-1][i];
}
for(int i=n-2;i>=0;i--)
{
for(int a=0;a<=i;a++)
{
dp[a]=max(dp[a],dp[a+1])+f[i][a];
}
}
cout<<dp[0];
}
五、P1020 [NOIP1999 普及组] 导弹拦截
题目:某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。输入导弹依次飞来的高度,计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
思路:先说一下,目前学到的求最长子序列的最优解,定义dp[len]数组,len表示当前最长子序列的长度,dp[n]数值表示序列第n个元素的值。第一个数会直接被储存在dp数组中,第二数如果大于dp数组的最后一个数的值(也就是当前序列最大值),则dp[++len]=第二个数(也就是把第二个数也存进dp,那len当然要同时加一了);但如果第二个数小于第一个数呢,第二个数会替换掉dp数组中第一个大于他的数,在这也就是第一个数。到这都好理解,但如果,这个数组是{1,3,5,2}。当循环到2时,此时dp={1,3,5},那么2会替换掉3,dp={1,2,5},dp数组的元素是不符合题干要求的,因为2应该在5的后面,但dp数组的长度确实和正确答案一致,怎么样,妙吧?
对于第一问,把题目数据反向输出,求出最长不降子序列,很像上面的第一题(但是你要注意一点,不能简单地将其等同于上升子序列,二分时要用upper_bound函数,这个函数会找出第一个大于f[i]的数;而lower_bound函数会找出第一个大于等于的函数。举个例子,对于{1,2,3,2,4,2,4},用upper得到的{1,2,2,2,4},lower得到的{1,2,3,4,4})。第二问,就是求有多少个不升子序列,也就是最长上升子序列的长度。
代码:
#include<iostream>
#include<algorithm>
using namespace std;
int main()
{
int f[100000],dp[100000];
int a = 0,len=0;
while (cin >> f[a]) a++;
dp[len] = f[a-1];
for (int i = a-2; i >=0; i--)
{
if (dp[len] <= f[i]) dp[++len] = f[i];
else
{
*upper_bound(dp, dp + len+1, f[i]) = f[i];
}
}
cout << ++len<<endl;
dp[0] = f[0];
len = 0;
for (int i = 1; i< a; i++)
{
if (dp[len] < f[i]) dp[++len] = f[i];
else
{
*lower_bound(dp, dp + len, f[i]) = f[i];
}
}
cout << ++len;
}