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;
}
posted @ 2024-02-28 13:04  zifanwang  阅读(25)  评论(0)    收藏  举报