CF756D Bacterial Melee 题解
给一个 \(O(n^2)\) 的做法。
考虑从左到右扫描最终得到的字符串,如果当前的字符和前一个字符相同,就删掉这个字符。由题意可知,最终得到的字符串一定是 \(s\) 的一个子序列。
我们定义状态 \(dp[i][j]\) 表示:当前子序列的最后一个字符是 \(s[i]\),已经填完了最终字符串的前 \(j\) 个字符,方案数是多少。
为了不重复计算子序列,我们再定义 \(ls[i][j]\) 表示 \(s\) 的前 \(i\) 个字符里所有为 \(j\) 的字符中最靠后的字符的位置。下面是求 \(ls\) 数组的方法:
for(int i=1;i<=n;++i){
for(int j=0;j<26;++j)ls[i][j]=ls[i-1][j];
ls[i][s[i]-'a']=i;
}
那接下来怎么求出 \(dp[i][j]\) 呢?我们可以把这 \(j\) 个字符全部填 \(s[i]\) 方案数是 \(1\),接下来我们可以枚举子序列的上一个字符 \(k\),我们可以得出转移方程:
\[dp[i][j]=1+\sum_{0\le k<25\land k\neq s[i]}\sum_{l=1}^{j-1}dp[ls[i-1][k]][l]
\]
我们可以用一个前缀和优化做到 \(O(26\cdot n^2)\),维护 \(d[i][j]\) 表示 \(\sum_{k=1}^{j}dp[i][k]\)。
那么怎么再优化到 \(O(n^2)\) 呢?
我们可以再维护一个 \(f[j]\) 表示 \(\sum_{i=0}^{25}dp[i][j]\) 和一个 \(ds[i]\) 表示 \(\sum_{j=1}^{i}f[j]\)。
下面是最终的转移方程:
\[dp[i][j]=1+ds[j-1]-d[i][j-1]
\]
代码:
#include<bits/stdc++.h>
#define mxn 5003
#define md 1000000007
#define rep(i,a,b) for(int i=a;i<=b;++i)
using namespace std;
int n,m,sum,f[mxn],dp[26][mxn],d[26][mxn],ds[mxn];
char s[mxn],t[mxn];
signed main(){
scanf("%d%s",&m,t+1);
rep(i,1,m)if(t[i]!=t[i-1])s[++n]=t[i];
rep(i,1,n){
int x=s[i]-'a';
rep(j,1,m){
sum=f[j],f[j]=(f[j]-dp[x][j])%md;
dp[x][j]=(ds[j-1]-d[x][j-1]+1)%md;
d[x][j]=(dp[x][j]+d[x][j-1])%md;
f[j]=(f[j]+dp[x][j])%md;
ds[j]=(ds[j-1]+f[j])%md;
}
}
cout<<(f[m]+md)%md;
return 0;
}

浙公网安备 33010602011771号