Gokix

一言(ヒトコト)

关注我

常见子序列DP模型

最长不下降(上升)子序列(LIS)

\(f_{len}\) 为长度为 \(len\) 的最长不下降子序列的最后一个数的最小值。

显然 \(f\) 单调不降。

\(cnt\) 为当前 \(f\) 更新到的最大长度。

正向枚举原数列 \(a\),如果 \(a_i \ge f_{cnt}\),表明可以继续把最长的子序列再增长一个长度,即 \(f_{++cnt}=a_i\)。而对于长度比 \(cnt\) 更短的 \(f\) 值,\(a_i\) 显然更新不了它们。因为 \(a_i\) 显然比他们大,显然我们希望最后一个数尽可能小,从而可以往后加更多的数。

如果 \(a_i<f_{cnt}\),则二分找出一个 \(f_j\) 使得其长度尽可能大并小于等于 \(a_i\),然后把 \(f_j\) 改成 \(a_i\)。显然 \(a_i\) 只能更新这一个 \(f\)

最后答案就是 \(cnt\)

时间复杂度 \(O(n \log n)\)

//LIS
memset(f,0x3f,sizeof(f));
f[1]=a[1];
cnt=1;
for(i=2;i<=n;i++){
	l=1;r=cnt+1;
	while(l<r){
		mid=(l+r)/2;
		if(f[mid]>a[i])
			r=mid;
		else
			l=mid+1;
	}
	f[l]=a[i];
	if(l==cnt+1) cnt++;
}
cout<<cnt<<endl;

最长公共子序列(LCS)

\(f_{i,j}\) 表示 \(a_1,a_2,...,a_{i}\)\(b_1,b_2,...,b_j\) 的最长公共子序列长度。

有如下转移方程:

\[f_{i,j}= \begin{cases} f_{i-1,j-1},a_i=b_j\\ \max({f_{i-1,j},f_{i,j-1}}),a_i \ne b_j\\ \end{cases} \]

时间复杂度 \(O(n^2)\)

n=s.size(); m=t.size();
for(i=1;i<=n;i++){
	for(j=1;j<=m;j++){
		if(s[i-1]==t[j-1]){
			f[i][j]=f[i-1][j-1]+1;
		}
		else{
			f[i][j]=max(f[i][j-1],f[i-1][j]);
		}
	}
}
cout<<f[n][m]<<endl;

如果给的 \(a\)\(b\) 是排列(P1439),那么有 \(O(n \log n)\) 的做法:挼行止爷爷的题解

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const long long N=1e5+1;
long long n,a[N+10],tr[N+10],f[N+10],cnt;

int main(){
	long long i,j,u,v;
	scanf("%lld",&n);
	for(i=1;i<=n;i++)
		scanf("%lld",&a[i]);
	for(i=1;i<=n;i++){
		scanf("%lld",&u);
		tr[u]=i;
	}
	for(i=1;i<=n;i++) a[i]=tr[a[i]];
	f[1]=a[1];cnt=1;
	long long l,r,mid;
	for(i=2;i<=n;i++){
		l=1,r=cnt+1;
		while(l<r){
			mid=l+r>>1;
			if(f[mid]>a[i]) r=mid;
			else l=mid+1;
		}
		f[l]=a[i];
		if(l>cnt) cnt++;
	}
	printf("%lld\n",cnt);
	return 0;
}

最长公共上升子序列(LCIS)

\(f_{i,j}\) 表示在 \(a\) 的前 \(i\) 个数,\(b\) 的前 \(j\) 个数,且以 \(b_j\) 结尾的 LCIS 长度。

有转移 \(\begin{cases} f_{i,j}=f_{i-1,j}\\f_{i,j}=\max{f_{i-1,k}}+1,a_i=b_j \end{cases}\)

发现后面那个转移可以记一下。于是就 \(O(n^2)\) 做完了。

输出方案的话记录一下前面更新的是哪一个就好了。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

inline long long read(){char ch=getchar();long long x=0,f=1;while(ch<'0' || ch>'9'){if(ch=='-') f=-1;ch=getchar();}
                        while('0'<=ch && ch<='9'){x=x*10+ch-'0';ch=getchar();}return x*f;}
inline void write(long long x){if(x<0){putchar('-');x=-x;}if(x>9) write(x/10);putchar(x%10+'0');}
const long long N=501;
long long n,a[N+10],m,b[N+10],f[N+10][N+10],ans,mx,id,pre[N+10][N+10],s[N+10],tot;

int main(){
	long long i,j,u,v;
	n=read();for(i=1;i<=n;i++) a[i]=read();
	m=read();for(i=1;i<=m;i++) b[i]=read();
	for(i=1;i<=n;i++){
		mx=0,id=0;
		for(j=1;j<=m;j++){
			pre[i][j]=pre[i-1][j];
			f[i][j]=f[i-1][j];
			if(a[i]==b[j]){
				if(f[i][j]<mx+1){
					f[i][j]=mx+1;
					pre[i][j]=id;
				}
			}
			else if(a[i]>b[j]){
				if(f[i-1][j]>mx){
					mx=f[i-1][j];
					id=j;
				}
			}
		}
	}
	id=0;
	for(i=1;i<=m;i++){
		if(ans<f[n][i]){
			ans=f[n][i];
			id=i;
		}
	}
	write(ans),putchar('\n');
	for(;id;id=pre[n][id]){
		s[++tot]=b[id];
	}
	while(tot){
		write(s[tot--]),putchar(' ');
	}
	putchar('\n');
	return 0;
} 
posted @ 2022-05-17 21:19  Gokix  阅读(45)  评论(0编辑  收藏  举报