题目
T组数据,每组数据给出n和m。有2个字符串A,B,每个字符串长为n,然后从m个字符中选择一些字符去填充字符串。要求A中出现的字符B不能再出现,即每个字符串只能在A或者B中出现,不能同时出现。然后一个字符串中某个字符可以出现任意次,排列顺序不同算不同种类。然后求最多有几种安排方案。
$ (T≤10), (1≤n,m≤2000)$
思路
- 先考虑字符串A,我们规定它选中了 x x x种字符,也就是说A中刚好 x x x种字符至少出现一次。然后此时B数组的选择就很简单,长度为 n n n,然后每个位置可以有 m − x m-x m−x(不能和A有相同字符)个选择。所以B数组的可能情况就是 ( m − x ) n (m-x)^n (m−x)n.
- 然后难点就在于如何求此时A数组的情况总数。枚举 x x x,然后我们需要求出A中刚好 x x x种字符至少出现一次的种类数。
- 先从 m m m个字符中选择 x x x个,即 C ( m , x ) C(m, x) C(m,x)
- 之后这个问题转化为可重复选择物品的排列数。这里是这样的,假设各物品是 a 1 , a 2 , . . . a n a_1, a_2,...a_n a1,a2,...an个,总和为 x x x,那么排列数就是$\frac {x!}{a_1!a_2!..a_n!} . 但 是 这 题 很 明 显 不 能 这 么 算 , 因 为 把 .但是这题很明显不能这么算,因为把 .但是这题很明显不能这么算,因为把x$分解明显是个很困难的问题.
- 这里就是主要用了一个容斥原理。公式是这样的:
C ( x , 0 ) x n − C ( x , 1 ) ( x − 1 ) n + C ( x , 2 ) ( x − 1 ) n + . . . C ( x , x ) 0 n C(x, 0)x^n-C(x, 1) (x-1)^n + C(x,2)(x-1)^n+...C(x,x)0^n C(x,0)xn−C(x,1)(x−1)n+C(x,2)(x−1)n+...C(x,x)0n
解释一下:先选0个不放,然后总共有 x n x^n xn种,然后减去1个不放的方案,再加上2个不放的方案数……最后就是每个种类至少放一次的方案数。
llong Repentance(int n, int m) //选了m个,n次方
{
int neg = 1;
llong tmp = 0;
for (int i=0; i<=m; i++)
{
tmp = (tmp + neg * C[m][i] * f[m - i][n] % mod) % mod;
tmp = (tmp + mod) % mod;
neg *= -1;
}
return tmp;
}
4.我只知道这个结论,但是不会证明,等我学完容斥再来mark……
- 然后大概的公式我们已经求解出来了。然后gzp写了一发发现TLE了。大概是枚举x,然后每个x又算容斥就是 O ( n 2 ) O(n^2) O(n2)的复杂度。同时思考这里有个快速幂的公式$x^n, (x-1)^n … 以 及 B 数 组 那 里 的 以及B数组那里的 以及B数组那里的(m-x)^n 。 这 里 。这里 。这里C 打 表 肯 定 每 个 人 都 想 得 到 , 然 后 我 第 一 次 见 到 了 给 幂 次 打 表 的 操 作 … … 上 面 那 个 代 码 的 第 7 行 的 打表肯定每个人都想得到,然后我第一次见到了给幂次打表的操作…… 上面那个代码的第7行的 打表肯定每个人都想得到,然后我第一次见到了给幂次打表的操作……上面那个代码的第7行的f[m-i][n] 就 是 就是 就是(m-i)^n , 这 里 打 了 个 表 , 所 以 每 次 只 要 ,这里打了个表,所以每次只要 ,这里打了个表,所以每次只要O(1)$取得幂次。
代码
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long llong;
const int maxn = 2000 + 10;
const int mod = 1e9 + 7;
llong C[maxn][maxn];
llong f[maxn][maxn];
void Pre_C()
{
C[0][0] = 1;
for (int i=1; i<maxn; i++)
{
C[i][0] = 1;
for (int j=1; j<=i; j++)
{
C[i][j] = (C[i-1][j-1] + C[i-1][j]) % mod;
//printf("C[%d][%d]=%lld\n", i, j, C[i][j]);
}
}
}
void Pre_fastpow()
{
for (int i=1; i<maxn; i++)
{
f[i][1] = i;
for (int j=2; j<maxn; j++)
f[i][j] = f[i][j-1] * i % mod;
}
}
llong Repentance(int n, int m) //选了m个,n次方
{
int neg = 1;
llong tmp = 0;
for (int i=0; i<=m; i++)
{
tmp = (tmp + neg * C[m][i] * f[m - i][n] % mod) % mod;
tmp = (tmp + mod) % mod;
neg *= -1;
}
return tmp;
}
int main()
{
Pre_C();
Pre_fastpow();
int T;
scanf("%d", &T);
while (T--)
{
int n, m;
scanf("%d %d", &n, &m);
llong ans = 0;
for (int i=1; i<m; i++)
{
ans = (ans + C[m][i] * Repentance(n, i) % mod * f[m - i][n] % mod) % mod;
}
printf("%lld\n", ans);
}
return 0;
}