[CodeForces-759D]Bacterial Melee
题目大意:
有一串n个字母,每个位置的字母可以同化边上的一个字母,
比如:ab可以变成aa或者bb。
相对的两个同化不能同时发生,比如ab不能变成ba。
现在给你一个字符串,问你经过任意次数的同化过程,最多能生成多少个字符串。
思路:
考虑同化过后的字符串与同化前的字符串的关系。
如果我们把一个字符串中相邻且相同的字母缩在一起,那么我们可以发现每一次同化就相当于从原串中去掉了一个字符。
这也就意味着同化过后的串一定是原串的一个子序列。
同样,如果一个串是原串的一个子序列,它一定能由原串同化而来。
我们可以先统计一下原串不同长度子序列的个数。
对于一个长度为l的子序列,它里面有n-l个字符被缩过,那么缩之前的串总共有C(n,l)种可能。
不同的子序列数量可以用DP求出来。
f[i][j]表示以字符j结尾的长度为i的子序列数量,则f[i][j]=sum{f[i-1][k]|k≠j}+1,枚举i,j,k,时间复杂度O(n^2*26)。
如果直接枚举k会TLE,只能过11个点,我们可以考虑用sum[i]记录长度为i的子串的数量和。
由于结尾位置的字符已确定,所以组合数用C(n-1,l-1)算,时间复杂度O(n^2)。
1 #include<cstdio> 2 #include<cctype> 3 typedef long long int64; 4 inline int getint() { 5 register char ch; 6 while(!isdigit(ch=getchar())); 7 register int x=ch^'0'; 8 while(isdigit(ch=getchar())) x=(((x<<2)+x)<<1)+(ch^'0'); 9 return x; 10 } 11 const int N=5001,SIGMA=27,mod=1e9+7; 12 char s[N]; 13 int f[N][SIGMA],sum[N],fact[N],factinv[N]; 14 inline int idx(const char &ch) { 15 return ch-'a'+1; 16 } 17 void exgcd(const int &a,const int &b,int &x,int &y) { 18 if(!b) { 19 x=1; 20 y=0; 21 return; 22 } 23 exgcd(b,a%b,y,x); 24 y-=a/b*x; 25 } 26 inline int inv(const int &x) { 27 int ret,tmp; 28 exgcd(x,mod,ret,tmp); 29 return (ret%mod+mod)%mod; 30 } 31 inline int C(const int &n,const int &m) { 32 return (int64)fact[n]*factinv[n-m]%mod*factinv[m]%mod; 33 } 34 int main() { 35 const int n=getint(); 36 scanf("%s",s); 37 fact[0]=factinv[0]=1; 38 for(register int i=1;i<n;i++) { 39 fact[i]=(int64)fact[i-1]*i%mod; 40 factinv[i]=inv(fact[i]); 41 } 42 sum[0]=1; 43 for(register int i=0;i<n;i++) { 44 const int ch=idx(s[i]); 45 for(register int i=1;i<=n;i++) { 46 sum[i]=(sum[i]-f[i][ch]+mod)%mod; 47 f[i][ch]=(sum[i-1]-f[i-1][ch]+mod)%mod; 48 sum[i]=(sum[i]+f[i][ch])%mod; 49 } 50 } 51 int ans=0; 52 for(register int i=1;i<=n;i++) { 53 ans=(ans+(int64)C(n-1,i-1)*sum[i]%mod)%mod; 54 } 55 printf("%d\n",ans); 56 return 0; 57 }