补8月10号~
9号那天搞的那道题,写了一些关于LIS的代码,后面想了想感觉不太对,试了几组数据果然有问题,属于是晚上思维混乱逻辑不清乱写一通,这里重新捋一遍吧。
一,题目
最长上升子序列(LIS,Longest Increasing Subsequence),指有一个长为n的数列a0, a1, ......, a(n-1)。求出这个序列中最长的上升子序列的长度。上升子序列指的是对于任意的i<j都满足ai<aj的子序列。
二,解法
1,dp 时间复杂度O( n^2 )
首先建两个数组一个读入、一个记录该位置之前所能组成的最大LIS,之后我们对于每一个数据,只要遍历出排在该数据前面的、比他小的、且LIS长度最长的那个数据然后加一,同时设置一个参数记录当前LIS长度的最大值。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+5,INF = 0x7f7f7f7f;
ll a[N],b[N],t,n,i,j,ma;
int main()
{
cin>>t;
while(t--){
ma=-INF;
cin>>n;
for(i=0;i<n;i++){ //读入数据,b[]存长度初始化
cin>>a[i];
b[i]=1;
}
for(i=0;i<n;i++){
for(j=0;j<i;j++)
if(a[i]>a[j])
b[i]=max(b[i],b[j]+1); //如果a[i]大于所遍历的数据,更新LIS长度
ma=max(ma,b[i]); //更新LIS长度最大值
}
cout<<ma<<endl;
}
return 0;
}
2,树状数组 时间复杂度O( nlog(n) )
上篇文章树状数组用的稀烂,此方法的思路仍是动态规划,只不过将数据结构利用二进制下标进行组合,从而达到缩短查找和维护数组所需要的大量时间。
与第一种方法不同的是,树状数组虽然利用了树状分支结构由上而下,每个节点存储其所有分支的信息,达到了可以快速定位的效果,在维护数组时就要注意不能只对该数据点进行单点更新,还要对其往上的所有结点进行更新,这样才能保证整个分支结构的完整性。
于是我们需要构建两个核心代码段:
//用于查找小于当前数据的数据点的LIS长度最大值(既当前数据可作为所找数据最长LIS的新一位)
for(int j=a[i]-1;j;j-=lowbit(j))
ma=max(ma,b[j]);
//用于更新当前数据点以及其所有父节点(即上一步找到的最大长度再加上当前数据的1)
ma++;
for(int j=a[i];j<=N2;j+=lowbit(j))
b[j]=max(b[j],ma);
以此为基础我们就能很快完善树状数组的解法(关于树状数组的结构上篇文章有简单提到,这里就不多叙述)
下面是完整代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+5,N2=1e8+6; //数据数量10^6、数据大小10^8
ll a[N],b[N2],t,n,ma,s;
int lowbit(int i){ //树状数组特有的遍历插值(喜)
return i&-i;
}
int main(){
cin>>t;
while(t--){
cin>>n;
s=0;
memset(b,0,sizeof(b)); //初始化
for(int i=0;i<n;i++){ //读入数据
cin>>a[i];
}
for(int i=0;i<n;i++){
ma=0;
for(int j=a[i]-1;j;j-=lowbit(j)) //查找最值
ma=max(ma,b[j]);
ma++;
for(int j=a[i];j<=N2;j+=lowbit(j)) //更新节点
b[j]=max(b[j],ma);
s=max(s,ma); //更新最大值
}
cout<<s<<endl;
}
return 0;
}
在上一篇文章中,我神奇的使用了构建结构体,对于读入的数据标号,然后按照数值进行排序,最后对标号进行动态规划,夸夸夸一顿操作,最后好像还八九不离十?事实上,这种方法脱裤子放屁不说,标号后排序,相当于把序号转换为了权值,把原来的数据变成了标号,乍一看好像还行得通,但是仔细想想,当输入的数据有重复的时候,如果重复数据所对应的LIS长度取最值,那么更新的时候就会自动加一,这是就会产生某些有趣的现象:
而昨天我的补救方法是读入数据的时候进行简单的去重,上面这个数据倒是对了,但还有下面这个:
这里程序处理掉了重复的3,但是对于隔开了一个的3,程序仍然进行了叠加,从而后面一个数据4也被读入,最后输出为4。这波属于是用了对的钥匙硬要反着拧了……看来网上的资料也不能全信(小声)
最后再给一下程序化的正确代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+5,N2=1e8+6; //数据数量10^6、数据大小10^8
ll a[N],b[N2],t,n,ma,s;
int lowbit(int i){ //树状数组特有的遍历插值(喜)
return i&-i;
}
void read(){
cin>>n;
for(int i=0;i<n;i++) //读入数据
cin>>a[i];
}
int chazhao(ll i){
ll ans=0;
for(;i;i-=lowbit(i)) //查找最值
ans=max(ans,b[i]);
return ans;
}
void gengxin(ll i,ll j){
for(;i<=N2;i+=lowbit(i)) //更新节点
b[i]=max(b[i],j);
}
int main(){
cin>>t;
while(t--){
read();
s=0;
memset(b,0,sizeof(b)); //初始化
for(int i=0;i<n;i++){
ma=chazhao(a[i]-1)+1;
gengxin(a[i],ma);
s=max(s,ma); //更新最大值
}
cout<<s<<endl;
}
return 0;
}