大江东去,浪淘尽,千古风流人物。故垒西边,人道是,三国周郎赤壁。乱石穿空,惊涛拍岸,卷起千堆雪。江山如画,一时多少豪杰。遥想公瑾当年,小乔初嫁了,雄姿英发。羽扇纶巾,谈笑间,樯橹灰飞烟灭。故国神游,多情应笑我,早生华发。人生如梦,一尊还酹江月。

最长上升子序列问题与最长公共子序列问题的复习

前言:刚学OI时太菜了,没有真正搞懂这些问题,现在希望加深对这些经典问题的理解。

最长上升子序列:

设dp[i]表示长度为i的上升子序列的最小末位数字。显然,长度一定时末尾数字越小越好。然后不断更新最小数字,显然若x>dp[i-1&&x<dp[i]&&x<dp[i+1],则x无法更新dp[i-1],但可以加在dp[i-1]后面构成长度为i的末尾数字更小的子序列。但无法构成一个长度为i+1的上升子序列。因为此前长度为i的上升子序列的末尾最小值都大于x,无法更新。

注意dp数组中存的是长度为i的上升子序列的最小末位数字,而不是最长上升子序列。因为我们加入x是只考虑了前面,没有考虑后面。对于前面,在末尾添加x可以正确的得到序列,但是对于后面的数只有在他加入序列那一刻,它前面的数才是正确的。故输出方案是可以记录加入时前面是那个数。有重复数字时需要记录下标。

代码(输出路径,以UVA-481为例):

#include<bits/stdc++.h>
using namespace std;

#define go(i,a,b) for(int i=a;i<=b;++i)
#define com(i,a,b) for(int i=a;i>=b;--i)
#define mem(a,b) memset(a,b,sizeof(a))
#define fo(i,a) for(int i(0);i<a;++i)

const int inf=0x3f3f3f3f;

const int N=1e6+10;

int n=1,dp[N],a[N],last[N],ans[N];

inline void read(int &x){
	x=0;char c=getchar(),f=1;
	while(!isdigit(c)){ if(c=='-') f=-1; c=getchar(); }
	while(isdigit(c)){ x=x*10+c-'0'; c=getchar(); }
	x*=f;
}

struct comp{
	bool operator ()(const int &i,const int &j){
		return a[i]<a[j];
	}
};

int main(){
	while(~scanf("%d",&a[n])) ++n;--n;
	a[0]=-inf;int len=0;
	go(i,1,n){
		if(a[i]>a[dp[len]]){
			dp[++len]=i;
			last[i]=dp[len-1];
		}
		else{
			int p=lower_bound(dp+1,dp+len+1,i,comp())-dp;
			dp[p]=i;last[i]=dp[p-1];
		}
	}
	printf("%d\n-\n",len);
	int x=dp[len];len=0;
	do{
		ans[++len]=x;
		x=last[x];
	}while(x!=0);
	com(i,len,1) printf("%d\n",a[ans[i]]);
	return 0;
}

最长公共子序列

\(O(n^2)\)做法众所周知,省略。这里主要谈谈\(O(nlogn)\)的做法

注意,该做法仅适用于a,b序列排序后完全相同的情况

算法:

c[i]数组表示a[i]在b数组中的下标,然后对c数组求一个最长上升子序列

正确性证明:

一个公共子序列,满足a[i]=b[j],a[k]=b[l],i<k,j<l。c数组下标表示数字在a中的下标,单调递增。c数组的值表示数字在b中的下标,需要满足单调递增,故问题转化为最长上升子序列。

代码略

posted @ 2019-10-15 09:42  White_star  阅读(150)  评论(0编辑  收藏  举报
}