Atcoder Typical DP Contest O 文字列

Description

构造一个小写字母构成的字符串,使小写字母 i i 出现了 f r e q i freq_i 次,且相同字母不能相邻,求方案数并对 1 0 9 + 7 10^9 + 7 取模。

1 f r e q i 10 1 \leq freq_i \leq10

Solution

计数 dp,先找出所有 f r e q i > 0 freq_i > 0 的小写字母,一个个插入字符串中。因为答案与插入顺序无关,所以我们按 aabbbccddddeeefgg 这样的顺序插入。在插入的过程中,难免会出现不合法的情况。举个栗子,如 *a a*c*b*d d d*b b*,不合法的位置有 4 4 个,即为空格。合法位置有 6 6 个,即为 *。可以发现三个性质:

  1. S S 的合法位置数与不和法位置数之和为 S + 1 |S| + 1
  2. 没有两个合法位置或不合法位置是相邻的。
  3. 每一个合法位置可以插入任意个字母,也可以不插。

所以容易想到状态,令 f i , j f_{i,j} 为插入了所有的小写字母 i i 后,有 j j 个不合法位置的方案数。赋初值,将第一段 f r e q i > 0 freq_i > 0 的小写字母全部插入后的 f i , j = 1 f_{i,j} = 1 。如 bbbccddddeeefggbbb 是第一段,于是 f b a = 1 , 2 = 1 f_{b - a = 1, 2} = 1

枚举 i i ,和 f i 1 , k f_{i-1,k} 中的 k k ,意味着 f i , j f_{i,j} 会从 f i 1 , k f_{i-1,k} 转移。考虑将所有的小写字母 i i 插入到字符串中,无非是插入到合法的位置或不合法的位置。所以再枚举一个 g o o d good b a d bad ,如字面意思, g o o d good 为插入到合法位置的段数 b a d bad 为插入到不合法位置的段数。为什么是段数而不是个数?根据性质二和三,插入的是一段相同的字母,且每段字母至长度至少为一,否则会重复计算答案。在枚举的过程中,同时维护一个在加入字母前的字符串长度 s u m sum ,用于确定枚举范围。

现在还没确定 j j 。考虑一个问题:将所有小写字母 i i 加入字符串,有 g o o d good 段字母插入了合法位置,有 b a d bad 段字母插入了不合法位置。那么新产生了多少不合法的位置,又减少了多少不合法的位置?

减少的简单,就是 g o o d good 个。可以发现,插入的字母分成了 g o o d + b a d good + bad 段,那么有 g o o d + b a d 1 good + bad - 1 个间隔,一个间隔由至少一个其他字母构成,如果紧密集合的话,可能成为不合法位置的位置一共有 f r e q i 1 freq_i - 1 个,所以增加了 f r e q i 1 g o o d b a d + 1 = f r e q i g o o d b a d freq_i - 1 - good - bad + 1 = freq_i - good - bad 个不合法位置。

终上所述, j = k b a d + f r e q i g o o d b a d j = k - bad + freq_i - good - bad

完事具备,只欠转移。那么转移方程如下,其中 s u m + 1 k sum + 1 - k 为根据性质一求出的合法位置个数, C m n C_{m}^n 为组合数可以预处理。可以发现,转移方程是根据乘法原理将每一部分相乘,其中的 C f r e q i g o o d b a d f i 1 C_{freq_i - good - bad}^{f_i - 1} 是什么呢?因为我们是把一段段相同的字母插入合法的位置或不合法的位置,不仅要乘上几段字母插入到哪几个位置的方案数,还要乘上所有的字母 i i 是如何分成了几段字母的方案数。把一段字母看成一个盒子,每个字母看成一个球,一共 g o o d + b a d good + bad 个盒子, f r e q i freq_i 个球,问题转换成求每个盒子至少一个球的方案数。套上插板法公式即可。

f i , j = f i , j + f i 1 , k × C b a d k × C g o o d s u m + 1 k × C g o o d + b a d 1 f r e q i 1 a n s = f n 1 , 0 f_{i,j} = f_{i,j} + f_{i - 1,k} \times C_{bad}^{k} \times C_{good}^{sum + 1 - k} \times C_{good + bad -1}^{freq_i - 1} \\ ans = f_{n-1,0}

时间复杂度 O ( n 3 ) O(n^3) ,其中 n = i = 0 25 f r e q i n = \sum_{i=0}^{25} freq_i

Code

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 500 + 5, p = 1e9 + 7;
inline int read() {
    int X = 0,w = 0; char ch = 0;
    while(!isdigit(ch)) {w |= ch == '-';ch = getchar();}
    while(isdigit(ch)) X = (X << 3) + (X << 1) + (ch ^ 48),ch = getchar();
    return w ? -X : X;
}
int f[26][N], C[N][N], freq[N], sum, fir, n;
signed main() {
	for (int i = 0; i < 26; i++) {
		freq[n] = read();
		if (freq[n]) n++;
	}
	for (int i = 0; i <= 300; i++) {
        C[i][0] = C[i][i] = 1;
        for (int j = 1; j < i; j++) C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % p;
	}
    f[0][freq[0] - 1] = 1, sum = freq[0];
    for (int i = 1; i < n; i++) {
    	for (int k = 0; k <= sum; k++) 
			for (int bad = 0; bad <= min(freq[i], k); bad++)
				for (int good = 0; good + bad <= freq[i] && good <= sum + 1 - k; good++) {
					int j = k - bad + freq[i] - good - bad;
					f[i][j] = (f[i][j] + f[i - 1][k] * C[k][bad] % p * C[sum + 1 - k][good] % p 
					* C[freq[i] - 1][good + bad - 1] % p) % p;
				}
		sum += freq[i]; 
    }	
	printf("%lld\n", f[n - 1][0]);
	return 0;
}  
发布了28 篇原创文章 · 获赞 38 · 访问量 507

猜你喜欢

转载自blog.csdn.net/qq_39984146/article/details/104209371