【Tai_mount】 算法学习 - 线性动态规划 - 最长上升子序列/最长公共子序列

主要是两道题:
导弹拦截
最长公共子序列

导弹拦截

前者题意即为求最长不上升子序列最长上升子序列

想一想应该就知道了。

看的是导弹拦截第一篇题解的方法。

先以最长不上升子序列为例。

我们有一个数组seq储存要处理的数列,另建一个同样大的数组ord。

从1到n一个个遍历seq中的元素,判断是否小于等于ord目前的最后一个元素。(ord初始是一个空的数列,逐渐被填满的),如果满足就往里面塞。不满足就它去替换序列里第一个小于他的元素。

这里面找到第一个小于他的元素是用STL实现的,后面再介绍。这个方法的逻辑在于:

  • 如果一直满足这显然就是个不上升的子序列
  • 如果不满足了,我们把这个数替换进去,就相当于前面大于等于这个数的序列还可以保留使用,从这个位置开始继续累积。而同时,可以同时有很多个这样的序列也在积累。其实就是这一个序列里同时判断了很多

啊啊啊啊啊啊啊我发现我真的很难描述,我我我我我我这东西真的靠自己理解。

算了反正大部分都是写给自己复习的,能看懂!

upper_bound找的是不带等号的,lowerbound找的是带等号的。参数是:要找的序列的第一个元素的地址,要找的序列的最后一个元素的后面一个元素的地址,设置的那个作为阈值的数,cmp函数。

默认是要求序列升序(一定是有序的啊!),这时找第一个大于那个数的。cmp和sort里面的差不多,可以找第一个小于那个数的,此时要求序列降序。

返回值是你要找的那个位置的指针。

Veritas跟我说的是,因为指针会有奇奇怪怪的错误,所以用这个指针减去数组的地址,就可以直接得到下标,这样不会出问题。

然后upper_bound和lower_bound是用的二分查找的办法,相信屏幕面前的自己应该是可以想明白的。也是logn的复杂度这样。

所以总的复杂度是nlogn。

上升下降不上升不下降……自己想一想全都能写出来了。


最长公共子序列

我是连着做这两道题的,在luogu的同一个题单里,所以我就想到用上面的方法解决下面的题。

给的两个序列是1n的不重复的数字,比如说给的是A序列和B序列吧。1n在A序列中都有一个唯一的位置。我们把这个位置信息替换B序列里实在的数——那么这道题就变成了求最长上升子序列了。

妙啊!

用upper和lower都可以,反正不会有重复的数

代码Tips

//导弹拦截
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=100007,MAXN=50007;
int seq[N],n;
void input(){
//	cin>>n;
//	for(int i=1;i<=n;i++){
//		cin>>seq[i];
//	}	
	int x;
	while(cin>>x){
		n++;
		seq[n]=x;
	}
}
int NoUpfun(){
	int len=0,ord[N];
	memset(ord,0,sizeof(ord));
	ord[0]=MAXN;
	for(int i=1;i<=n;i++){
		if(ord[len]>=seq[i]){
			len++; ord[len]=seq[i];
			continue;
		}
		int p=upper_bound(ord+1,ord+len,seq[i],greater<int>())-ord;
		ord[p]=seq[i];
	}
	return len;
}
int Upfun(){
	int len=0,ord[N];
	memset(ord,0,sizeof(ord));
	for(int i=1;i<=n;i++){
		if(ord[len]<seq[i]){
			len++; ord[len]=seq[i];
			continue;
		}
		int p=lower_bound(ord+1,ord+len+1,seq[i])-ord;
		ord[p]=seq[i];
	}
	return len;
}
void output(){
	cout<<NoUpfun()<<'\n'<<Upfun();
}
int main(){
	input();
	output(); 
	return 0;
}
最长公共子序列
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=100007;
int n,base[N],seq[N],ord[N];

void input(){
	cin>>n;
	int x;
	for(int i=1;i<=n;i++){
		cin>>x;
		base[x]=i;
	}
	for(int i=1;i<=n;i++){
		cin>>x;
		seq[i]=base[x];
	}
}
int Upfun(){
	int len=0;
	for(int i=1;i<=n;i++){
		if(ord[len]<seq[i]){
			ord[++len]=seq[i];
			continue;
		}
		int p=upper_bound(ord+1,ord+len+1,seq[i])-ord;
		ord[p]=seq[i];
	}
	return len;
}
//int Upfun(){
//	int len=0;
//	for(int i=1;i<=n;i++){
//		if(ord[len]<seq[i]){
//			len++; ord[len]=seq[i];
//			continue;
//		}
//		int p=lower_bound(ord+1,ord+len,seq[i])-ord;
//		ord[p]=seq[i];
//	}
//	return len;
//}
void output(){
	cout<<Upfun();
}
int main(){
	input();
	output();
	return 0;
}

一开始我STL函数里参数都写的ord,ord+len,后面那个其实应该是ord+len+1。但莫名的都没错。其实差别就在于没有判断最后一个元素……

以最长不上升为例,进到这个步骤的元素(比如叫x),x>ord[len]。我们要找的是第一个<x的元素,所以……这个元素永远都不会是满足条件,所以虽然我写错了但并没有完全写错。

……

另外是一度把len写成了n,调了好久。

另外把len++; ord[len]=seq[i]改成ord[++len]=seq[i]会更快

最后我没看出来这是动态规划……

posted @ 2021-07-18 17:22  Tai_mount  阅读(61)  评论(0)    收藏  举报