快速幂
对于一个数的次幂,如:210。最常规的计算方法是将2乘以10次,时间复杂度为:O(N)。
如果我们将它写成二进制:210102,很自然的可以分解成210002与2102两个数相乘,即:28∗22。这样,我们可以在计算28时使用已经计算完毕的24,所以我们只需要将底数不断的平方就可以计算出答案,时间复杂度:O(log2N)。
long long qpow(long long n, long long m)
{
long long ans = 1;
while (m)
{
if (m & 1)
ans *= n;
n *= n;
m >>= 1;
}
return ans;
}
排列组合
1.逆元
自然数a,p互质,如果满足a*b%p=1,那么b为a%p的逆元
2.小费马定理
公式:a与p互质,ap−1%p=1
证明:
1.如果自然数c和p互质,a∗c%p=b∗c%p,那么我们可以得到a%p=b%p
设a=l∗p+q, b=r∗p+q ⇒ a%p=b%p ⇔ a−b=p∗ (l−r)
a∗c%p=b∗c%p
⇒ (a∗c−b∗c) %p=0
⇒ (a−b) ∗c%p=0
因为c和p是互质的,⇒ (a−b) %p=0
⇒ a%p=b%p
2.定义集合c1,c2,c3,c4...cp−1是p的一个完全剩余系,a与p互质,那么a∗c1,a∗c2,a∗c3...a∗cp−1也是p的一个完全剩余系。
假设a∗cn%p=a∗cm%p,那么cn%p=cm%p。所以命题成立。
1∗2∗3∗4∗...∗ (p−1) %p=ap−1∗1∗2∗3∗4∗...∗ (p−1) %p
显然1∗2∗3∗4∗...∗ (p−1)与p互质,所以ap−1%p=1%p
证毕
3.排列组合
C(n,m)=n!/(n−m)!∗m!
用一个数组存储106以内的阶乘,再利用公式进行运算结果。注意的是我们计算的时候出现了取余mod,这会导致不能直接进行除法运算,我们需要将分子与分母的逆元相乘即可。
费马定理:a∗ap−2%p=1
根据上面证明,可以得出ap−2%p是a的逆元
const int N = 1e6 + 20;
long long int da[N], db[N];
long long C(long long n, long long m)
{
if (n <= m)
return 1;
return da[n] * db[n - m] % mod * db[m] % mod;
}
main函数内代码
da[0] = 1, db[0] = 1;
for (int i = 1; i <= N; i++)
{
da[i] = da[i - 1] * i % mod;
db[i] = db[i - 1] * qpow(i, mod - 2) % mod;
}
容斥原理及二项式反演
容斥原理公式:
A1∪A2∪A3∪...An=∑1≤i≤nAi−∑1≤i≤j≤nAi∩Aj+∑1≤i≤j≤k≤nAi∩Aj∩Ak−...+(−1)n−1∗A1∩A2∩A3∩...An
⇒ ∑k=1n(−1)k−1∑1≤i≤j≤...≤k≤nAi∩Aj∩...∩Ak
证明:定义一个元素被m个元素所共有
∑k=1m(−1)k−1C(m,k)=C(m,1)−C(m,2)+C(m,3)−...+(−1)m−1∗C(m,m)
⇒ C(m,0)−C(m,0)+C(m,1)−C(m,2)+C(m,3)−...+(−1)k−1C(m,m)
⇒ 1−∑k=0n(−1)k∗C(m,k)
⇒ 1−(1−1)m=1
这说明右式的每个元素都仅仅算了一次
二项式反演:
定义f(n)为任意n个集合的交集的元素个数,g(n)为任意n个集合的补集的交集的元素个数。
特别的定义f(0)=U,g(0)=U,U代表全集
A1∪A2∪...∪Ak=A1∩A2∩...∩Ak
⇒ U−A1∩A2∩...∩Ak=A1∪A2∪...∪Ak
⇒ A1∩A2∩...∩Ak=U−A1∪A2∪...∪Ak
即g(n)=U−∑k=1n(−1)k−1∑1≤i≤j≤...≤k≤nAi∩Aj∩...∩Ak
容斥原理公式转变(细看容斥原理证明)
⇒ g(n)=U−∑k=1n(−1)k−1∗C(n,k)∗f(k)
又因为f(0)=U, ⇒ g(n)=∑k=0n(−1)k∗C(n,k)∗f(k)
如果我们把补集与原集合进行颠倒,公式仍然成立。
即:f(n)=∑k=0n(−1)k∗C(n,k)∗g(k)
通用公式:f(n)=∑k=inC(n,k)∗g(k) ⇒ g(n)=∑k=in(−1)n−k∗c(n,k)∗f(k)
题解
用不超过k种颜色涂满n个盒子,要求相邻的盒子颜色不同,方案数为:xk=k∗(k−1)n−1
从k种颜色种选择i种颜色涂满n个盒子,要求相邻的盒子颜色不同,方案数最多为:yk=C(k,i)∗i∗(i−1)n−1,这里面一定有重复的。比如我们提前挑选出1,2这两种颜色,然后再从剩下的颜色中随机选一种颜色,有k-2种可能,但是这些可能的情况中,只用1,2颜色涂色的情况都被算过。
根据题意可以写出:
定义tk表示恰好用k种颜色涂满n个盒子,相邻盒子颜色不同的方案数
xn=∑k=1nC(n,k)∗tk=C(n,1)∗t1+C(n,2)∗t2+...C(n,n)∗tn
根据二项式反演,得:tn=∑k=1n(−1)n−k∗xk
然后共有m种颜色,挑出k种颜色涂满n个盒子的方案数即为:
ans=C(m,k)∗tn
注:m的数据范围高达109,所以使用公式:m∗(m−1)∗(m−2)∗...∗(m−n+1)/n!
AC代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long int;
using ull = unsigned long long int;
using pll = pair<ll, ll>;
const ll mod = 1e9 + 7;
long long qpow(long long n, long long m)
{
if (n == 0 || n == 1)
return n;
long long ans = 1;
while (m)
{
if (m & 1)
ans = ans * n % mod;
n = n * n % mod;
m >>= 1;
}
return ans % mod;
}
const int N = 1e6 + 20;
long long int da[N], db[N];
//da表示阶乘,db表示逆元
long long C(long long n, long long m)
{
if (n <= m)
return 1;
return da[n] * db[n - m] % mod * db[m] % mod;
}
void dilingtian()
{
long long n, m, k;
cin >> n >> m >> k;
//特例,只有一个盒子的时候
if (n == 1)
{
if (k == 1)
cout << m << endl;
else
cout << 0 << endl;
return;
}
long long x = 1;
long long ans = 0;
for (long long i = k; i >= 1; i--)
//根据公式发现,第k个元素一定为正,所以反向计算
{
ans = (ans + x * C(k, i) * i % mod * qpow(i - 1, n - 1) % mod + mod) % mod;
x *= -1;
}
for (ll i = m - k + 1; i <= m; i++)
ans = ans * i % mod;
ans = ans * db[k] % mod;
cout << (ans + mod) % mod << endl;
}
int main(void)
{
da[0] = 1, db[0] = 1;
for (int i = 1; i <= N; i++)
{
da[i] = da[i - 1] * i % mod;
db[i] = db[i - 1] * qpow(i, mod - 2) % mod;
}
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t;
cin >> t;
while (t--)
dilingtian();
return 0;
}