Strivore题解
题目
给出一个字符串 \(S\) , 往这个字符串的任意位置插入 \(n\) 个字符, 问最终会形成多少个不同的字符串。
思路详解
看到这道题, 相信很多人的第一反应是:既然是任意位置, 那不就随便放吗? 最后肯定都不一样。 接着, 就用 \(C^{|S| - 1}_{|S| + n - 1} * 26^n\) (这里指盒子放球问题的公式与计算字符排列), 接着就惊喜的看到了 Wrong Answer。
这是因为少考虑了一个问题——重复字符。比如对于一个字母 \(o\) , 我们在它的左边加上一个 \(o\) 和在右边加是一样的, 都会变成 \(oo\) 。 以此类推, 对于任意一个字符串中的字母, 如果插入了一个与其相同的字母, 并且新插入的字母在原字母与其在原字符串中相邻字母之间, 那么新插入的字母在原字母的左边还是右边并无影响, 只有个数会有影响。 那么, 我们就必须必须想一个方法避免这个问题。
最好的方法就是把所有相同的字符强制加到一边, 再转化为一边不能加与自己相同的。 这样做的好处是对于一连串相同的字母, 如 \(ooooooo\) , 可以发现相同字符不仅插入两边等价, 插在中间也是等价的。 而将限制条件转化为了一边不允许加与自己相同的, 那么中间也不能加了, 也就不用再考虑这些问题了。
假设我们确定的是左边不能加与自己相同的, 那么就会的到一个很神奇(这里指显然)的性质, 除去加入到最右边的那些字符, 其他字符只有 \(25\) 种情况。也就是说, 若我们已经确定了每一个要插入字符的位置且没有确定到底是哪个字符的情况下, 插入在原字符串右边的字符可以变成 \(a\) ~ \(z\) 中任意一个, 插入在中间和左边的可以变成 \(a\) ~ \(z\) 中除去被限制不能取的那一个以外的另外任意一个。那么, 如果是这样的话, 那么先不考虑颜色, 枚举最右边插入了多少个字符, 在将左边和中间当成可以空箱球无区别箱有区别的盒子放球问题, 再乘上染色的方案数就可以了。
综上所述, 得到公式: \(总方案数 = \sum^{n}_{i = 0}25^{n - i} * C_{n - i + m - 1}^{m - 1} * 26^i\)
代码:
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
#define MAXN 2000000
#define INF 1000000007
char c[MAXN + 5];
long long s[MAXN + 5];
long long Pow (long long a, int b) {
long long ans = 1;
while (b) {
if (b & 1) {
ans *= a;
ans %= INF;
}
a *= a;
a %= INF;
b >>= 1;
}
return ans;
}
long long C(int b, int a) {
return s[b] * Pow (s[b - a] * s[a] % INF, INF - 2) % INF;
}
int main () {
int n;
int m;
scanf ("%d", &n);
scanf ("%s", c + 1);
m = strlen (c + 1);
s[0] = 1;
for (int i = 1; i <= n + m; i ++) {
s[i] = s[i - 1] * i % INF;
}//存储阶乘, 方便后面计算组合数
long long sum = 0;
//枚举右边插入字符数
for (int i = 0; i <= n; i ++) {
sum += Pow (25, n - i)/*中间与左边的染色方案数*/ * C(n - i + m - 1, m - 1)/*中间和左边的排列方案数*/ % INF * Pow (26, i)/*右边的染色方案数*/ % INF;
sum %= INF;
}
printf ("%lld", sum);
}

浙公网安备 33010602011771号