最长上升子序列问题(LIS):(dp&树状数组复盘

补8月10号~

9号那天搞的那道题,写了一些关于LIS的代码,后面想了想感觉不太对,试了几组数据果然有问题,属于是晚上思维混乱逻辑不清乱写一通,这里重新捋一遍吧。

日练23.8.9(补)_只为藜藿的博客-CSDN博客

 一,题目

最长上升子序列(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;
} 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值