目录
1:题目简介
2:方法分析
3:DP做法(60分)
4:二分做法(100分)
题目传送门:错误 - 重庆八中OJhttp://222.180.160.110:1024/contest/3078/problem/12
题目简介
给定一个序列,从中选取若干个数,使得这一组数组成的序列a满足 i < j 且 a[i] < a[j],求这个序列的最长长度
1:DP做法
关键点:找到状态转移方程与递推式,完成此题
思路:设定数组d为前i个数的最长子序列,ans为最后的值
每次比较a[i]和a[j],(i<j),如果a[i]>=a[j],d[i]=max(d[i],d[j]+1)(判断是之前中断过的序列更长还是现在的序列更长
由此,我们就可以推出来状态转移方程式
d[i]=max(d[i],d[j]+1)(d[i]>=d[j])(j<i)
边界:d[1]=1;
DP代码:
#include<bits/stdc++.h>
using namespace std;
const int M=1e6+10;
int d[M],a[M],ans,n;
int dp(int n){
d[1]=1;
ans=1;
for (int i=2;i<=n;i++){
d[i]=1;
for (int j=1;j<i;j++){
if (a[i]>a[j]){
d[i]=max(d[i],d[j]+1);
ans=max(ans,d[i]);
}
}
}
return ans;
}
int main(){
cin >> n;
for (int i=1;i<=n;i++){
cin >> a[i];
}
cout << dp(n);
}
OK,我们能够清晰的看到,TLE,60分
这是为什么呢? 数据太大了
原因:动态规划约等于加强版的递推
而递推的特点就是,推回去再推回来
示例:
求5的阶乘:
边界:1的阶乘为1
状态转移方程:jc[i]=jc[i-1]*i;
图例:
jc(5)=jc(4)*5
jc(4)=jc(3)*4
jc(3)=jc(2)*3
jc(2)=jc(1)*2
jc(1)=1;
到这里,已经推到底了,然后还要再乘回去
约等于双重循环!
DP算法虽然还可以加上记忆化搜索来节省时间,但是面对本题1e5的数据,还是太慢了
好了,接下来介绍二分做法
二分,就是折半查找,比如1~100的随机数,暴力寻找要找100次,二分只用7次
核心思想:先判断是否大于五十
如果大于50,前面50个数就可以不用寻找了
再判断是否大于75......
相当于一下子就可以筛掉原数据的50%,节省了大量时间!找n个数中的一个数的次数m也从m=n变为了n=2的m次方
效率提升了不少
关键:如何折半查找(以什么为目标来折半查找)
思路:还是先定义ans数组来储存子序列
如果第i个数大于第i-1个数的话,为了确保这个子序列是最长的,应当保证该子序列的第i项与第i-1项的差最小
那么如果我们在数组中发现了更小的数而且大于数列中的一个数的话,就可以用这个数来取代那一个数了
而我们如何找到一个恰好比新输入的数大但是新输入的数又比那个数的前一个数小的序列的位置呢?
二分!
先对整个序列里面的数进行折半查找,然后找到数之后再插入进去,最后得出整个序列的长度
二分100分代码:
#include<bits/stdc++.h>
using namespace std;
const int M=1e6+10;
int n,cnt;
int a[M],ans[M];
int main(){
cin >> n;
for (int i=1;i<=n;i++){
cin >> a[i];
}
ans[1]=a[1];
cnt=1;
for (int i=2;i<=n;i++){
if (a[i]>ans[cnt]){
ans[++cnt]=a[i];
}
else {
int l=1,r=cnt,mid;
while(l<r){
mid=(l+r)/2;
if (ans[mid]>=a[i]){
r=mid;
}
else{
l=mid+1;
}
}
ans[l]=a[i];
}
}
cout << cnt << endl;
}
好了,关于这道题的介绍就到这里了,再见