Acwing进阶课–数论
目录:
线性基
线性基:是竞赛中常用来解决子集异或一类题目的算法。
线性基:利用高斯消元求出一组矩阵的基。
基的性质:(
梦回线代)
- 线性基中每个元素的二进制最高位互不相同
- 线性基的维度 = 矩阵的秩
- 向量满秩–> 向量线性无关;向量不满秩–>向量线性相关。
构造线性基:
int k = 0;
for(int i = 62; i >= 0; i --)
{
for(int j = k; j < n; j ++)//找到第i位为1的数,交换至第k行
if(a[j] >> i & 1)
{
swap(a[k], a[j]);
break;
}
if(!(a[k] >> i & 1)) continue;//第i为全为0
for(int j = 0; j < n; j ++)
if((a[j] >> i & 1) && j != k) a[j] ^= a[k];
k ++;
//当行秩<列秩(数的个数 < 62)时:秩 <= 行秩;k == n+1:时可以直接break;
//不加这一句话,i也只会枚举63次,多枚举的63-n次循环里面都是“continue”。
if(k == n) break;
}
例题:
异或运算
线性基板子
// int_max (1 << 31) - 1 取 30
// LL_max (1 << 63) - 1 取 62
// uLL_max (1 << 64) - 1 取 63
// 注意求 kth rank之时必须要提前 rebuild
template<typename T, const size_t len = 62>struct LineBase
{
int cnt;
T d[len + 1], p[len + 1];
T f[len + 1];
bool zero, build;
void init()
{
for (int i = 0; i < len + 1; ++ i) d[i] = p[i] = f[i] = 0;
zero = build = false;
cnt = 0;
}
void insert(T x)
{
for (int i = len; ~i; -- i)
{
if(!((x >> i) & 1)) continue;
if(!d[i])
{
d[i] = x;
++ cnt;
return ;
}
x ^= d[i];
}
zero = true;
}
// 判断是否能被异或出来
bool exist(T x)
{
for (int i = len; ~i; -- i)
if(((x >> i) & 1))
{
if(d[i]) x ^= d[i];
else return false;
}
return true;
}
void rebuild()
{
cnt = 0;
build = true;
for (int i = len; ~i; -- i)
for (int j = i - 1; ~j; -- j)
if((d[i] >> j) & 1) d[i] ^= d[j];
for (int i = 0; i <= len; ++ i)
if(d[i]) p[cnt ++] = d[i];
}
//查询异或最大,最小
T maxx(T res = 0)
{
for (int i = len; ~i; -- i) res = max(res, res ^ d[i]);
return res;
}
T minn()
{
if(zero) return 0;
for (int i = 0; i <= len; ++ i)
if(d[i]) return d[i];
return -1;
}
// 必须选取
// 注意题意中是否能不选 能不选则要考虑0
// k 名次 (从小到大)
T query_k(T k)
{
if(!build) rebuild();
if(k >= (((T)1) << cnt)) return -1; // 无解
if(!k) return 0;
T res = 0;
for (int i = len; ~i; -- i)
if((k >> i) & 1) res ^= p[i];
return res;
}
T kmx(T k)
{
if(!build) rebuild();
if(k <= 0) return -1;
T num = (((T)1) << cnt);
if(!zero) -- num;
return kmn(num - k + 1);
}
T kmn(T k)
{
if(!build) rebuild();
if(k <= 0) return -1;
return k - zero ? kth(k - zero) : 0;
}
// 从小到大排 且要求这个数必须能被线性基异或出来才可以查询rank
T rank(T x)
{
if(!build) rebuild();
T res = 0;
for (int i = cnt - 1; ~i; -- i)
if(x >= p[i])
{
res += (((T)1) << i);
x ^= p[i];
}
return res + zero;
}
// 获取此时能获得的元素的个数
T checkall()
{
return (((T)1) << cnt) - (!zero);
}
};
LineBase<LL> t;
斯特林数
第一类斯特林数
这里指的为第一类无符号斯特林数。
表示: s ( n , m ) s(n, m) s(n,m)或 [ n k ] \left[{n \atop k} \right] [kn]
意义:第一类斯特林数(斯特林轮换数) [ n k ] \left[{n \atop k} \right] [kn] 表示将 n n n 个两两不同的元素,划分为 k k k个非空圆排列的方案数。圆排列定义:圆排列是排列的一种,指从 n 个不同元素中取出 m(1≤m≤n)个不同的元素排列成一个环形,既无头也无尾。两个圆排列相同当且仅当所取元素的个数相同并且元素取法一致,在环上的排列顺序一致。
递推式:
性质:
- 与升阶函数的关系:
Code:
void init()
{
s[0][0] = 1;
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= m; j ++)
s[i][j] = (s[i - 1][j - 1] + 1ll * (i - 1) * s[i - 1][j]) % mod;
}
第二类斯特林数
表示: S ( n , k ) S(n, k) S(n,k) 或 { n k } \left\{\begin{matrix} n \\ k \end {matrix}\right \} {nk}
意义: 第二类斯特林数(斯特林子集数) { n k } \left\{\begin{matrix} n \\ k \end {matrix}\right \} {nk}表示将 n 个两两不同的元素,划分为 k 个非空子集的方案数。
递推式:
性质:
通项公式:
Code:
void init()
{
S[0][0] = 1;
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= m; j ++)
S[i][j] = (S[i - 1][j - 1] + 1ll * j * S[i - 1][j]) % P;
}
两种斯特林数的关系:
例题:建筑师
生成函数
具体可以参考这篇Blog:生成函数全家桶
本人口胡简述:
- 将利用组合数求方案数转变为利用多项式求方案数(多项式,函数发展较成熟)。
- 定义:
任意给定一个无限长的序列: a 0 , a 1 , a 2 , . . . , a n , . . . a_0, a_1, a_2, ... , a_n, ... a0,a1,a2,...,an,...
定义函数 g ( x ) = a 0 x 0 + a 1 x 1 + a 2 x 2 + . . . + a n x n + . . . g(x) = a_0x^0+a_1x^1+a_2x^2+...+a_nx^n+... g(x)=a0x0+a1x1+a2x2+...+anxn+...
这是一个无穷级数,我们一般设 x x x的取值范围为: − 1 < x < 1 -1 < x < 1 −1<x<1,因为显然在这范围内,无穷级数收敛,我们就可以在 x → ∞ x \rightarrow \infty x→∞的时候得到该无穷级数的值。
我们称这个函数 g ( x ) g(x) g(x)为序列的生成函数。实际上就是将方案数的序列映射成一个多项式的系数,将计数问题转化为多项式问题。例如我们得到生成函数以后,我们就可以使用求极限,泰勒展开,求导积分…
下面举个例子来更好的理解生成函数。
Apple
Problem:有 n n n种 a p p l e apple apple,每种 a p p l e apple apple都有无限个,求选出 k − 1 k-1 k−1个 a p p l e apple apple的方案数。
- 组合数学的解法:
因为每一种 apple 都有无限个,也就是说每一种都可以选择 1 , 2 , 3 , 4 , . . . , n , . . . 1, 2, 3, 4, ... , n, ... 1,2,3,4,...,n,... 个
设第 i i i中 a p p l e apple apple选 a i a_i ai个
则: a 1 + a 2 + a 3 + . . . + a n = k a_1 + a_2 + a_3 + ... + a_n = k a1+a2+a3+...+an=k; a i ≥ 0 a_i \geq 0 ai≥0sadf
使用隔板法,隔板法要求 a i ≥ 1 a_i \geq 1 ai≥1,所以我们设: a i ′ = a i + 1 a_i\prime = a_i + 1 ai′=ai+1
代入: a 1 ′ + a 2 ′ + a 3 ′ + . . . + a n ′ = k + n a_1\prime + a_2\prime + a_3\prime + ... + a_n\prime = k + n a1′+a2′+a3′+...+an′=k+n; a i ′ ≥ 1 a_i\prime \geq 1 ai′≥1
可以看做共有 k + n k + n k+n个小球,我们再小球的间隙里,放置 n − 1 n - 1 n−1个隔板,把这 k + n k+n k+n个小球隔开,分为 n n n块,(因为每一块之间至少有一个小球,所以才必须要求 a i ≥ 1 a_i \geq 1 ai≥1),从前往后依次对应 x 1 ′ , x 2 ′ , . . . , a n ′ x_1\prime, x_2\prime, ..., a_n\prime x1′,x2′,...,an′所分配的小球的个数,即为当前的方案,任意的一种隔板的放置方案,对应着方程组的一组解。
可得答案为: C k + n − 1 n − 1 = C_{k+n-1} ^ {n - 1} = Ck+n−1n−1= ( k + n − 1 n − 1 ) {k+n-1} \choose {n-1} (n−1k+n−1)
- 生成函数的解法:
我们对于第一种 a p p l e apple apple:可以选 0 , 1 , 2 , . . . , n 0,1, 2,...,n 0,1,2,...,n个。
f 1 ( x ) = 1 + x + x 2 + x 3 + . . . f_1(x) = 1 + x + x^2 + x^3 + ... f1(x)=1+x+x2+x3+...
f 1 ( x ) = 1 − x n 1 − x f_1(x) = \frac{1 - x^n}{1-x} f1(x)=1−x1−xn
lim x → ∞ f 1 ( x ) = 1 1 − x \lim_{x \rightarrow\infty} f_1(x) = \frac{1}{1-x} limx→∞f1(x)=1−x1
同理: f 2 ( x ) = 1 1 − x f_2(x) = \frac{1}{1-x} f2(x)=1−x1, f 3 ( x ) = 1 1 − x f_3(x) = \frac{1}{1-x} f3(x)=1−x1,… , f n ( x ) = 1 1 − x f_n(x) = \frac{1}{1-x} fn(x)=1−x1
所以最终:
f ( x ) = f 1 ( x ) ∗ f 2 ( x ) ∗ f 3 ( x ) ∗ . . . ∗ f n ( x ) = 1 ( 1 − x ) n f(x) = f_1(x) * f_2(x) * f_3(x) * ... * f_n(x)\\=\frac{1}{(1-x)^{n}} f(x)=f1(x)∗f2(x)∗f3(x)∗...∗fn(x)=(1−x)n1 = a 0 x 0 + a 1 x 1 + a 2 x 2 + . . . + a k x k = a_{0}x^{0} + a_1x^{1} + a_2x^2+...+a_kx^k =a0x0+a1x1+a2x2+...+akxk
其中 a k a_k ak对应我们上面求得的公式:
a k = C k + n − 1 m − 1 a_k = C_{k + n -1} ^ {m-1} ak=Ck+n−1m−1结论:
1 ( 1 − x ) n \frac{1}{(1-x)^{n}} (1−x)n1中 x k x^k xk的系数为: C k + n − 1 m − 1 C_{k+n-1}^{m - 1} Ck+n−1m−1。表示: n n n种 a p p l e apple apple,个数无限,选 k k k个的方案数。
例题:食物
FFT
这里墙裂安利一片Blog:超简单的快速傅里叶变换!
本人口胡简述:
FFT :快速傅里叶变换。
用于:快速求出两个多项式的乘积(卷积);大数 × \times ×大数(大数快速幂)。
Code:
const int N = 5e5 + 10;
const double PI = acos(-1);
struct Complex
{
double x, y;
Complex operator + (const Complex &t) const
{
return {x + t.x, y + t.y};
}
Complex operator - (const Complex &t) const
{
return {x - t.x, y - t.y};
}
Complex operator * (const Complex &t) const
{
return {x * t.x - y * t.y, y * t.x + x * t.y};
}
}a[N], b[N];
int tot, bit;
int tra[N];//蝴蝶转换
void fft(Complex a[], int type)
{
for(int i = 0; i < tot; i ++)
if(i < tra[i]) swap(a[tra[i]], a[i]);//当i<tra[i]时交换:避免数交换两次,以至于没转换。
for(int mid = 1; mid < tot; mid <<= 1)
{
auto w1 = Complex({cos(PI / mid), type * sin(PI / mid)});
for(int pos = 0, len = mid << 1; pos < tot; pos += len)
{
auto wk = Complex({1, 0});
for(int k = 0; k < mid; k ++, wk = wk * w1)
{
//左半部分
Complex x = a[pos + k];
//右半部分
Complex y = a[pos + k + mid];
a[pos + k] = x + wk * y;
a[pos + k + mid] = x - wk * y;
}
}
}
if(type == 1) return;
rep(i, 0, tot) a[i].x /= tot;
}
int main()
{
ios;
int n, m;
cin >> n >> m;
rep(i, 0, n) cin >> a[i].x;
rep(i, 0, m) cin >> b[i].x;
//n, m分别是A多项式,B多项式的最高次
while((1 << bit) <= n + m) bit ++;
tot = 1 << bit;
rep(i, 0, tot - 1)//处理转换数组
tra[i] = ((tra[i / 2] / 2) | (i & 1) << (bit - 1));
// exit(0);
fft(a, 1), fft(b, 1);
rep(i, 0, tot) a[i] = a[i] * b[i];
fft(a, -1);
rep(i, 0, n + m) cout << int(a[i].x + 0.5) << ' ';
return 0;
}
BSGS 和 扩展BSGS(北上广深)
BSGS(Baby Step Giant Step)
给定正整数
a
,
b
,
P
a,b,P
a,b,P;
(
a
,
P
)
=
1
即
a
,
P
互质
(a, P) =1即a,P互质
(a,P)=1即a,P互质。
求满足
a
t
≡
b
(
m
o
d
P
)
a^t\equiv b(mod\ P)
at≡b(mod P)
的最小非负整数
t
t
t。
由于 ( a , P ) = 1 (a, P) = 1 (a,P)=1,
⇒ a φ ( P ) ≡ 1 ( m o d P ) \Rightarrow a^{\varphi(P)} \equiv1\ (mod\ P) ⇒aφ(P)≡1 (mod P),可以看出 a t a^t at在模 P P P意义下的最小循环节为 φ ( P ) \varphi(P) φ(P).
⇒ \Rightarrow ⇒所以我们要找 t t t范围是: t ∈ [ 0 ∼ φ ( P ) ] t\in[0 ~ \sim \varphi(P)] t∈[0 ∼φ(P)]
⇒ \Rightarrow ⇒扩大范围: t ∈ [ 0 ∼ P ] t\in [0 \sim P] t∈[0∼P]
⇒ \Rightarrow ⇒令: k = P + 1 k = \sqrt{P} + 1 k=P+1(+1因为计算机开根误差);
⇒ \Rightarrow ⇒ t = k ∗ x − y t = k*x-y t=k∗x−y( x ∈ [ 1 ∼ k ] x\in[1 \sim k] x∈[1∼k]; y ∈ [ 0 ∼ ( k − 1 ) ] y\in[0\sim(k -1)] y∈[0∼(k−1)],这里x,y可以看做: x = t / k , y = t % k x=t/k, y=t\%k x=t/k,y=t%k)。
⇒ \Rightarrow ⇒ t m i n = k − ( k − 1 ) = 1 t_{min} = k - (k-1)= 1 tmin=k−(k−1)=1; t m a x = k ∗ k = k 2 = P t_{max} = k * k = k ^2 = P tmax=k∗k=k2=P
⇒ \Rightarrow ⇒ t ∈ [ 1 ∼ P ] t \in[1\sim P] t∈[1∼P]。这里 t t t少一种 0 0 0,故我们应先特判 t = 0 t= 0 t=0的情况。
⇒ \Rightarrow ⇒特判 0 0 0之后,在 x ∈ [ 1 ∼ k ] x\in[1\sim k] x∈[1∼k]和 y ∈ [ 0 ∼ k − 1 ] y \in [0\sim k-1] y∈[0∼k−1]中找到: t = k ∗ x − y t = k * x - y t=k∗x−y,满足:
a t ≡ b ( m o d P ) a^t \equiv b (mod\ P) at≡b(mod P)
⇒ \Rightarrow ⇒ a k ∗ x − y ≡ b ( m o d P ) a^{k*x - y} \equiv b (mod\ P) ak∗x−y≡b(mod P)
⇒ \Rightarrow ⇒ a k ∗ x ≡ b ∗ a y ( m o d P ) a^{k*x} \equiv b * a^y(mod\ P) ak∗x≡b∗ay(mod P)
⇒ \Rightarrow ⇒不能二重循环找 x , y x,y x,y,这样为 O ( k 2 = P ) O(k^2 = P) O(k2=P)
⇒ \Rightarrow ⇒对于每一个 x x x, y y y都要跑 [ 0 ∼ k − 1 ] [0\sim k-1] [0∼k−1]。
⇒ \Rightarrow ⇒先预处理,每一个 y y y对应的 b ∗ a y ( % P ) b*a^y(\%P) b∗ay(%P);再跑 x ∈ [ 1 ∼ k ] x\in [1\sim k] x∈[1∼k]判断是否存在 b ∗ a y ( % P ) = a k x ( % P ) b*a^y(\%P) = a^{kx}(\% P) b∗ay(%P)=akx(%P)即可。
⇒ \Rightarrow ⇒时间复杂度: O ( k = P ) O(k = \sqrt{P}) O(k=P)。
Code:
int a, p, b;
int BSGS(int a, int p, int b)
{
//特判t=0
if(1 % p == b % p) return 0;
//t = kx-y。x--[1 ~ k];y--[0 ~ k-1]
int k = sqrt(p) + 1;
//遍历y,预处理(b*a^y) % p
unordered_map<int, int> hash;
for(int i = 0, w = b % p; i <= k - 1; i ++, w = 1ll * w * a % p)
hash[w] = i;
//求a^k
int ak = 1;
for(int i = 1; i <= k; i ++) ak = 1ll * ak * a % p;
//遍历x
for(int i = 1, w = ak; i <= k; i ++, w = 1ll * w * ak % p)
if(hash.count(w)) return (k * i - hash[w]) % p;
return -1;
}
扩展BSGS
求最小非负整数 t t t,使得 a t ≡ b ( % P ) a^t\equiv b(\% P) at≡b(%P); a 和 P 不一定互质 a和P不一定互质 a和P不一定互质。
1:特判 t = 0 t = 0 t=0的情况。
2: t ≥ 1 t \geq 1 t≥1:a , P a,P a,P最大公约数: d = ( a , P ) d = (a, P) d=(a,P)
①: a , P 互质 − − d = 1 a, P互质--d = 1 a,P互质−−d=1。直接BSGS。
②: d > 1 d > 1 d>1
a t ≡ b ( % P ) a^t\equiv b(\% P) at≡b(%P)
⇒ \Rightarrow ⇒ a t + k P = b ( % P ) a ^ t+kP = b(\% P) at+kP=b(%P)
因为 d d d为 a a a的约数, d d d为 P P P的约数;由裴蜀定理得: d d d为 b b b的约数。
⇒ \Rightarrow ⇒故当 b % d ≠ 0 即 d 不是 b 的约数 b \% d \neq 0即d不是b的约数 b%d=0即d不是b的约数;无解。
⇒ \Rightarrow ⇒当 b % d = 0 b \% d = 0 b%d=0:
⇒ \Rightarrow ⇒ a t − 1 ∗ a d + k ∗ p d = b d a^{t-1} * \frac{a}{d} + k * \frac{p}{d} = \frac{b}{d} at−1∗da+k∗dp=db
⇒ \Rightarrow ⇒ a t − 1 ∗ a d ≡ b d ( % P d ) a^{t -1} * \frac{a}{d} \equiv \frac{b}{d}(\% \frac{P}{d}) at−1∗da≡db(%dP)
⇒ \Rightarrow ⇒ a t − 1 ≡ P d ∗ i n v ( a d ) [ a b 关于 P d 的逆元 ] ( % P ) a^{t-1} \equiv \frac{P}{d} * inv(\frac{a}{d})[\frac{a}{b}关于\frac{P}{d}的逆元] (\% P) at−1≡dP∗inv(da)[ba关于dP的逆元](%P)–由于 P d \frac{P}{d} dP不一定是质数,求逆元要用扩展欧几里得。
(由于 P P P的因子有限,最多递归 l o g 2 P log_2P log2P次, a t ‘ 与 P ‘ 互质 a^{t`}与P`互质 at‘与P‘互质,这时直接调用BSGS。)
Code:
int a, p, b;
int exgcd(int a, int b, int &x, int &y)
{
if(!b)
{
x = 1, y = 0;
return a;
}
int d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
int bsgs(int a, int p, int b)
{
if(1 % p == b % p) return 0;
int k = sqrt(p) + 1;
unordered_map<int, int> hash;
for(int i = 0, w = b % p; i < k; i ++, w = 1ll * w * a % p)
hash[w] = i;
int ak = 1;
for(int i = 1; i <= k; i ++) ak = 1ll * ak * a % p;
for(int i = 1, w = ak % p; i <= k; i ++, w = 1ll * w * ak % p)
if(hash.count(w)) return k * i - hash[w];
return -1e9;
}
int exbsgs(int a, int p, int b)
{
//逆元x可能为负,这里的b也可能是负数
b = (b % p + p) % p;
if(1 % p == b % p) return 0;
int x, y;
int d = exgcd(a, p, x, y);
if(d == 1) return bsgs(a, p, b);
if(b % d) return -1e9;
//x是(a/d) 关于 (p/d)的逆元,可能为负数
exgcd(a / d, p / d, x, y);
return 1 + exbsgs(a, p / d, 1ll * b / d * x % (p / d));
}
int main()
{
while(cin >> a >> p >> b , a || p || b)
{
int res = exbsgs(a, p, b);
if(res < 0) puts("No Solution");
else cout << res << endl;
}
return 0;
}
例题:扩展BSGS
Burnside引理和polya定理
参考资料
主要用来解决 “本质不同” 的计数问题。(如:用m种颜色给立方体,珠链染色)
前置知识:
置换:
有限集合到自身的双射(即一一对应)称为置换。集合 S = { a 1 , a 2 , … , a n } S = \{a_1,a_2,\dots,a_n\} S={a1,a2,…,an}上的置换可以表示为: f = ( a 1 , a 2 , … , a n a p 1 , a p 2 , … , a p n ) f = \begin{pmatrix}a_1,a_2,\dots,a_n\\a_{p_1},a_{p_2},\dots,a_{p_n}\end{pmatrix} f=(a1,a2,…,anap1,ap2,…,apn)
意为将 a i a_i ai映射为 a p i a_{p_i} api,其中 p 1 , P 2 , … , p n p_1, P_2, \dots, p_n p1,P2,…,pn是 1 , 2 , … , n 1, 2, \dots, n 1,2,…,n的一个排列。显然 S S S上所有置换的数量为 n ! n! n!。循环置换:
循环置换是一类特殊的置换,可表示为
( a 1 , a 2 , … , a m ) = ( a 1 , a 2 , … , a m − 1 , a m a 2 , a 3 , … , a m , a 1 ) (a_1,a_2,\dots,a_m) = \begin{pmatrix}a_1,a_2,\dots,a_{m-1},a_m\\a_2,a_3,\dots,a_m,a_1\end{pmatrix} (a1,a2,…,am)=(a1,a2,…,am−1,ama2,a3,…,am,a1)
若两个循环置换不含有相同的元素,则称它们是不相交的。有如下定理:
任意一个置换都可以分解为若干不相交的循环置换的乘积,例如
( a 1 , a 2 , a 3 , a 4 , a 5 a 3 , a 1 , a 5 , a 4 ) = ( a 1 , a 3 , a 2 ) ∘ ( a 4 , a 5 ) \begin{pmatrix}a_1, a_2,a_3,a_4,a_5\\a_3,a_1,a_5,a_4\end{pmatrix}=(a_1,a_3,a_2)\circ(a_4,a_5) (a1,a2,a3,a4,a5a3,a1,a5,a4)=(a1,a3,a2)∘(a4,a5)
该定理的证明也非常简单。如果把元素视为图的节点,映射关系为有向边,则每个点的入度个出度都为 1 1 1,因此形成的图形必定是若干个环的集合,而一个环即可用一个循环置换表示。
Burnside 引理
总结版:每种置换的不动点个数的平均值,即为==“本质不同”== 的方案数。
Polya 定理
总结版:引入“循环群”,每种置换都可以拆为多个互不干扰的“循环群”(因为每个点的出度个入度都为1,所以一定是形成多个环),因此每个“循环群的”颜色也是互不干扰的。故:每种置换的不动点 = 颜色种 类 { 该置换可拆为循环群的数量 } = 颜色种类^{\{该置换可拆为循环群的数量\}} =颜色种类{该置换可拆为循环群的数量}。
可以发现:polya就是找到一种计算Burnside中每种置换不动点个数的方法。
- 注:当置换能分为互不干扰的循环群才能进一步使用Polya定理;不然还是要是用Burnside引理!
这里举个例子:
问题:用 M M M种颜色,给 N N N个珠子组成的环形手链染色,有多少中方案。这里要求本质不同的方案数。(即:两种染色方式经旋转或翻转能完全重合,则视为同一种手链。)
可以看出存在旋转和翻转两种置换,我们分别分析这两种置换:
旋转:
我们令其每个点旋转 K K K次即: a i 旋转至 a i + K , ( K ∈ [ 0 ∼ n − 1 ] ) a_i 旋转至 a_{i+K},(K \in [0\sim n-1]) ai旋转至ai+K,(K∈[0∼n−1])
令 d = g c d ( N , K ) d = gcd(N, K) d=gcd(N,K),可以发现:每个点转 N d \frac{N}{d} dN次,可以回到出发点。证明:从 i i i出发,每次跳 K K K步,求最小 a a a。
要满足: i + a ∗ K ≡ i ( % N ) i + a*K \equiv i(\% N) i+a∗K≡i(%N)
⇒ \Rightarrow ⇒ a ∗ K ≡ 0 ( % N ) a*K\equiv 0(\%N) a∗K≡0(%N)
当 a = N d = g c d ( N , K ) 时: a=\frac{N}{d = gcd(N,K)}时: a=d=gcd(N,K)N时: ⇒ \Rightarrow ⇒ N ∗ K d ≡ 0 ( % N ) N * \frac{K}{d} \equiv 0(\%N) N∗dK≡0(%N),可以发现 N K 与 N 互质 \frac{N}{K}与N互质 KN与N互质,此时的 a a a最小。所以这个置换可以拆分为 N N d = d \frac{N}{\frac{N}{d}} = d dNN=d个循环置换,每个循环置换之间的颜色互不干扰。该置换的方案数: M ( n , K ) M^{(n, K)} M(n,K)
故旋转置换的方案数为: ∑ K = 0 N − 1 M ( N , K ) \sum\limits_{K = 0}^{N-1}{M^{(N, K)}} K=0∑N−1M(N,K)。
翻转:N为偶数:
- 两个点形成一条对称轴: N 2 \frac{N}{2} 2N条,每条对称轴形成 N 2 + 1 \frac{N}{2}+1 2N+1个循环群。
- 两点之间的空隙形成一条对称轴: N 2 \frac{N}{2} 2N条,每条轴形成 N 2 \frac{N}{2} 2N个循环群。
N为偶数方案数: N 2 ∗ ( M N 2 + M N 2 + 1 ) \frac{N}{2}*(M^{\frac{N}{2}} + M^{\frac{N}{2} + 1}) 2N∗(M2N+M2N+1)
N为奇数:
- 每个点形成一条对称轴: N N N条,每条对称轴形成 N + 1 2 \frac{N+1}{2} 2N+1个置换群。
N为奇数方案数: N ∗ M N + 1 2 N * M ^ {\frac{N+1}{2}} N∗M2N+1
故翻转置换方案数为: f ( x ) = { N 为偶数: N 2 ∗ ( M N 2 + M N 2 + 1 ) N 为奇数: N + 1 2 f(x)=\left\{ \begin{aligned} N为偶数:& \frac{N}{2}*(M^{\frac{N}{2}} + M^{\frac{N}{2} + 1}) \\ N为奇数:& \frac{N+1}{2} \\ \end{aligned} \right. f(x)=⎩ ⎨ ⎧N为偶数:N为奇数:2N∗(M2N+M2N+1)2N+1
注:当置换没法拆分为互不干扰的置换群时,无法使用Polya定理
例:魔法手链:这里的染色方法有约束,无法将置换拆分为互不干扰的置换群,只能使用Burnside引理。
积性函数
定义:
定义在正整数上的函数,
∀
g
c
d
(
a
,
b
)
=
1
,
f
(
a
,
b
)
=
f
(
a
)
f
(
b
)
\forall gcd(a,b) = 1, f(a,b) = f(a)f(b)
∀gcd(a,b)=1,f(a,b)=f(a)f(b)
凡是积性函数均可使用线性筛法来求解。例: μ ( i ∗ p j ) = μ ( i ) ∗ μ ( p j ) = − μ ( i ) , p j 为质数。 \mu (i*p_j) = \mu(i) * \mu(p_j) = -\mu(i),p_j为质数。 μ(i∗pj)=μ(i)∗μ(pj)=−μ(i),pj为质数。
- μ ( x ) \mu(x) μ(x)是积性函数。
证明:
a = p 1 α 1 × p 2 α 2 × ⋯ × p n α n a = p_{1}^{\alpha1} \times p_{2}^{\alpha2} \times \dots \times p_{n}^{\alpha n} a=p1α1×p2α2×⋯×pnαn
b = p 1 β 1 × p 2 β 2 × ⋯ × p n β n b = p_{1}^{\beta1} \times p_{2}^{\beta2} \times \dots \times p_{n}^{\beta n} b=p1β1×p2β2×⋯×pnβn
a × b = p 1 α 1 × p 2 α 2 × ⋯ × p n α n × p 1 β 1 × p 2 β 2 × ⋯ × p n β n a \times b = p_{1}^{\alpha1} \times p_{2}^{\alpha2} \times \dots \times p_{n}^{\alpha n} \times p_{1}^{\beta1} \times p_{2}^{\beta2} \times \dots \times p_{n}^{\beta n} a×b=p1α1×p2α2×⋯×pnαn×p1β1×p2β2×⋯×pnβn
- 若 μ ( a ) = 0 \mu(a) = 0 μ(a)=0 或 μ ( b ) = 0 \mu(b) = 0 μ(b)=0,则 μ ( a × b ) = 0 \mu(a \times b) = 0 μ(a×b)=0。即: μ ( a × b ) = μ ( a ) × μ ( b ) \mu(a \times b) = \mu(a) \times \mu(b) μ(a×b)=μ(a)×μ(b)
- 若 μ ( a ) ≠ 0 \mu(a) \neq 0 μ(a)=0或 μ ( b ) ≠ 0 \mu(b) \neq 0 μ(b)=0,则 μ ( a × b ) = ( − 1 ) k + t = ( − 1 ) k × ( − 1 ) t = μ ( a ) × μ ( b ) \mu(a\times b) = (-1)^{k + t} = (-1)^k \times (-1)^t = \mu(a) \times \mu(b) μ(a×b)=(−1)k+t=(−1)k×(−1)t=μ(a)×μ(b)
- φ ( x ) \varphi(x) φ(x)是积性函数。
证明:
两种莫比乌斯反演的方式的证明(第二种更常用)
前置知识:
证明:
铅笔佬的证明(也有数论分块的证明)
本人~~(蒟蒻)~~的证明:
第一种
- 若有 F ( n ) = ∑ d ∣ n f ( d ) F(n) = \sum_{d|n}{f(d)} F(n)=∑d∣nf(d),则: f ( n ) = ∑ d ∣ n μ ( d ) ∗ F ( n d ) f(n) = \sum_{d|n}{\mu(d) * F(\frac{n}{d})} f(n)=∑d∣nμ(d)∗F(dn)
- 遍历 n n n的所有约数 d d d
证明:
第二种
- 若有 ∑ n ∣ d f ( d ) \sum_{n|d}{f(d)} ∑n∣df(d),则: f ( n ) = ∑ n ∣ d μ ( d n ) ∗ F ( d ) f(n) = \sum_{n|d}{\mu(\frac{d}{n}) * F(d)} f(n)=∑n∣dμ(nd)∗F(d)
- 在题目所给数据范围内遍历 n n n的所有倍数 d d d
证明:
总结:
莫比乌斯反演其实就是一个定理:若有 ∑ d ∣ n f ( d ) \sum_{d|n}{f(d)} ∑d∣nf(d),则: f ( n ) = ∑ d ∣ n μ ( d ) ∗ F ( n d ) f(n) = \sum_{d|n}{\mu(d) * F(\frac{n}{d})} f(n)=∑d∣nμ(d)∗F(dn)
关键在于:找到一个满足条件的 F ( n ) F(n) F(n) [ F ( n ) F(n) F(n)要好求]和 f ( n ) f(n) f(n) [ f ( n ) f(n) f(n)不好求],通过上述莫比乌斯反演将不好求的 f ( n ) f(n) f(n)用好求的 F ( n ) F(n) F(n)表示。