线性dp

经典问题

最长上升子序列

C. 老司机的狂欢

二分以后可以发现每个司机的起点和终点就确定了,那么按照起点排序排序后求终点的 \(LIS\)


B. 降雷皇

考虑 \(O(n)\) 求解 \(LIS\) 的方案数
那么可以发现 \(j\) 可以转移到 \(i\) 的条件是 \(f_j=f_i-1\) 并且 \(a_j<a_i\)
可以发现如果把所有 \(f_i\) 的位置拿出来,那么可以发现所有位置的 \(a\) 是单调递减的
直接双指针扫描即可


D. Sanrd

首先有性质 \(LIS\)\(LDS\) 最多有一个交点
那么如果一个 \(LIS\) 与所有 \(LDS\) 都相交则不合法,否则一定合法
求出一个辅助值 \(f_i\) 表示强制选 \(i\)\(LDS\) 方案数
然后找到两个 \(\sum f\) 不同的 \(LIS\) 即可


P2501 [HAOI2006]数字序列

由于 \(a\) 是严格单调的,中间可能不能变过去
那么对于 \(i,j\) 来说,如果可以相邻,那么 \(i,j\) 必须满足 \(a_i-a_j\ge i-j\)
那么对于 \(b_i=a_i-i\)\(LIS\) 即可
对于第二问,有结论是对于一个在 \(LIS\) 中的区间 \([i,j]\),之间的数一定存在一个分界点 \(k\),使得 \([i+1,k]\) 都变成 \(b_i\)\([k+1,j-1]\) 都变成 \(b_j\)


最长公共子序列

朴素的 \(dp\)\(f[i][j]=max(f[i-1][j],f[i][j-1],f[i-1][j-1]+1[a_i=b_j]\)
\(a,b\) 是排列时可以将 \(b\)\(a\) 排序然后跑 \(LIS\)
对于方案数,在朴素统计时需要注意如果 \(a_i!=b_j\) 是需要减去 \(f[i-1][j-1]\)(类似于二维前缀和)


杂题

C. 抛硬币

求长度为 \(l\) 本质不同子序列个数

\(f[i][j]\) 表示 \(i\) 位置长度为 \(j\) 的子序列个数
那么 \(f[i][j]=f[i-1][j-1]+f[i-1][j]-f[last_{i-1}][j-1]\)

可以发现在这种条件下用容斥的减法会变得简洁

代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=3005;
const int mod=998244353;
char c[maxn];
int len,n,last[130],f[maxn][maxn],ans,cnt,l[maxn];
bool vis[maxn];
signed main(){
	scanf("%s",c+1);
	cin>>len;
	n=strlen(c+1);
	for(int i=1;i<=n;i++){
		l[i]=last[c[i]];
		last[c[i]]=i;
		if(!vis[c[i]])cnt++,vis[c[i]]=1;
		f[i][1]=cnt;
		if(!l[i])l[i]++;
//		cout<<l[i]<<" ";
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=len;j++){
			if(!f[i][j])f[i][j]=(((f[i][j]+f[i-1][j])%mod+f[i-1][j-1])%mod-f[l[i]-1][j-1]+mod)%mod;	
		}
	}
	cout<<(f[n][len]%mod+mod)%mod;
	return 0;
}

链式反应

似乎这种二叉树类型的题常常是用这种设树根的方式解决
\(f_i\) 表示大小为 \(i\) 的子树方案数
那么直接枚举左右大小,然后一直乘组合数就好了
最神奇的是这个组合数拆开后可以用递推出来,貌似组合数常常会有这种情况

posted @ 2022-08-26 19:50  y_cx  阅读(45)  评论(0编辑  收藏  举报