[算法模版]子序列DP
Posted gavinzheng
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[算法模版]子序列DP相关的知识,希望对你有一定的参考价值。
[算法模版]子序列DP
如何求本质不同子序列个数?
朴素DP
复杂度为(O(nq))。其中(q)为字符集大小。
(dp[i])代表以第(i)个数结尾的本质不同子序列个数。注意,这里对于每一个字符,只计算上一个相同字符带来的贡献。如果全部计算的话会算重复。
最后统计答案的时候也只统计每个字符最后一次出现的位置的答案。
例题:【线上训练13】子序列 中的50分部分分
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxn=2e6+100,mod=998244353;
typedef long long ll;
char s[2][maxn],ori[maxn];
int n,len=1;
int last[30];
ll dp[maxn];
void init() {
scanf("%s",ori+1);
s[1][1]=ori[1];
for(int i=2;i<=n;i++) {
int md=i&1;
for(int j=1;j<=len;j++) {
s[md][2*j-1]=ori[i];
s[md][2*j]=s[md^1][j];
}
s[md][2*len+1]=ori[i];
len=2*len+1;
}
}
int main() {
scanf("%d",&n);
init();
scanf("%s",s[n&1]+1);len=strlen(s[n&1]+1);
for(int i=1;i<=len;i++) {
int ty=s[n&1][i]-'a';
dp[i]=1;
for(int j=0;j<26;j++) {
if(last[j]){
dp[i]+=dp[last[j]];
}
}
dp[i]%=mod;
last[ty]=i;
}
ll ans=0;
for(int i=0;i<26;i++) {
if(last[i])ans+=dp[last[i]];
ans%=mod;
}
ans%=mod;
printf("%lld",ans);
}
优化DP
我们令(f[i]=sum_{j=1}^q dp[last[q]])。其中(q)为字符集大小
转移的时候有两种情况:
- 当前字符未出现过(第一个)。那么令(dp[i]=f[i-1]+1),则(f[i]=2 imes f[i-1]+1)。
- 当前字符在原来出现过。那么令(dp[i]=f[i-1]+1)。因为只记录最后一次,所以在给(f)加上这一次的(dp)值之后还要删去上一次的(dp)值。(f[i]=2 imes f[i-1]-dp[last[i]]+1)。因为(dp[last[i]]=f[last[i]-1]+1),所以(f[i]=2 imes f[i-1]-f[last[i]-1])
其实本质就是利用子序列DP每次增加的都是(sum dp[每一个字符])这个特性,每次转移将(sum dp[每一个字符])用(O(1))复杂度起来,做到(O(1))的转移。
算法复杂度(O(n))
例题:FZU-2129
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
int n,a[1000500],f[1000500],last[1000500],ans=0;
const int mod=1e9+7;
int main() {
ios::sync_with_stdio(0);
while(cin>>n) {
for(int i=1;i<=n;i++)cin>>a[i];ans=0;
memset(f,0,sizeof(f));memset(last,0,sizeof(last));
for(int i=1;i<=n;i++) {
if(!last[a[i]])f[i]=(2ll*f[i-1]+1)%mod;
else f[i]=(2ll*f[i-1]-f[last[a[i]]-1])%mod;
last[a[i]]=i;
}
ans=(f[n]%mod+mod)%mod;
cout<<ans<<endl;
}
return 0;
}
以上是关于[算法模版]子序列DP的主要内容,如果未能解决你的问题,请参考以下文章
[bzoj1966][Ahoi2005][VIRUS 病毒检测] (字符串dp)