大体题意:
给你n 个位置,你总共有m 种颜色,需要填满恰好填k种颜色且 相邻的位置的颜色不相同,问方案数,两个方案不同 区分为至少有一个位置的颜色的不同!
思路:
我们先选出k 种颜色来, 方案数 是 C(m,k)。
然后在n 个位置依次填, 第一个位置 有k 种情况,第二个位置 有k-1个情况 第三个位置有k-1个情况!这样就是
k*(k-1) ^ (n-1) 这些情况只是包含了两个相邻位置不相同,并不能保证恰好有k 种颜色!
这里用到了容斥定理,我们用总共k 种颜色的减去 k-1种颜色 的 即减去 (k-1) * (k-2) ^ (n-1),减去后并不是 答案!
因为减的时候会减多了,举一个例子:
假设你有4个位置, 让你填恰好 4种颜色!
假设让你在4种框子里涂4种颜色 ,恰好为4种!
那么你先用 4 * 3 * 3 * 3 总共四种颜色的情况 减去 三种颜色的 C(4,3) * 3 * 2 * 2 * 2
但这样会减多了两种的 比如是 一个减了 1 2 3 颜色 又减了 2 3 4 这样 2 3 这样两种颜色的就减多了!所以在加回来两种颜色的! 假设后面还有的化 这样就又多加了 1种颜色 的 再减去! 这样加一次减一次 。就是容斥定理了!
因此 答案就是 C(m,k) * (C(K,K) * K * (K-1) ^ (n-1) - c(k,k-1) * (k-1)*(k-2) ^(n-1) +.... c(k,1)*1*0^(n-1))
注意 枚举要枚举到1,因为可能 会只有1个位置的边界情况!
但是这样还没有完事!
求 k的多少次方 肯定是快速幂了!
k的范围很小 为100w,因此 为了加快速度,我们可以先预处理出来 c(m,k) 和 c(k,k)所有情况!
递推有除法,用到了逆元!
如果求逆元用快速幂的话,依然会超时!
虽然mod 很大,但是只用到了k 以内的数据!
因此 我们也可以预处理 递推求出逆元来!
详细见代码:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int mod = 1000000007;
const int maxn = 1000000 + 7;
ll my_pow(ll a,ll n){
ll ans = 1ll;
while(n){
if (n & 1)
ans = (ans * a) % mod;
a = (a * a) % mod;
n >>= 1;
}
return ans;
}
int n, m, k, ks, T;
ll ck[maxn], cm[maxn];
ll inv[maxn];
void initinv(){ // 递推求逆元
inv[1] = 1;
for (int i = 2; i < maxn; ++i) {
inv[i] = (mod - mod/i) * inv[mod % i] % mod;
}
}
void init(){
ck[0] = cm[0] = 1; // 预处理所有的组合数!
for (int i = 1; i <= k; ++i) {
ck[i] = ((ck[i-1] * (k-i+1)) % mod * inv[i]) % mod;
cm[i] = ((cm[i-1] * (m-i+1)) % mod * inv[i]) % mod;
}
}
int main(){
initinv();
scanf("%d", &T);
while(T--){
scanf("%d %d %d", &n, &m, &k);
init();
if (n < k || m < k){
printf("Case #%d: %d\n",++ks,0);
continue;
}
ll ans = 0;
for (int i = k, j = 1; i >= 1; --i,++j){ // 容斥定理的 枚举
if (j & 1)
ans = (ans + ((i * ck[i]) % mod * my_pow(i-1,n-1)) % mod + mod) % mod;
else
ans = (ans - ((i * ck[i]) % mod * my_pow(i-1,n-1)) % mod + mod) % mod;
}
ans = (ans * cm[k]) % mod;
printf("Case #%d: %lld\n",++ks,ans);
}
return 0;
}