[Burnside引理+數論] HDU 6960 Necklace of Beads
阿新 • • 發佈:2021-07-21
題目大意
有一個由 \(n\) 個珠子組成的項鍊,珠子有紅綠藍三種顏色,要求項鍊中相鄰的珠子不能同色,求綠色珠子數量不超過 \(k\) 的本質不同項鍊的總數。若兩條項鍊能通過順時針旋轉變得相同,則認為這兩條項鍊本質相同。\(1\leq n,k\leq 10^**6\)
題解
考慮 Burnside 引理:
\[|X/G|=\frac{1}{|G|}\sum_{g\in G} |X^g| \]設 \(g(n,m)\) 表示對長度為 \(n\) 的環進行染色,在綠色的個數為 \(m\) 的情況下,染色的方案數(不考慮本質不同)。當順時針旋轉 \(i\) 步時,圓環被劃分為 \(\mathrm{gcd}(n,i)\)
而對於一個 \(n\) 個點的圓環,一共有 \(n\) 個置換,由Burnside引理,有:
\[ans=\frac{1}{n}\sum_{i=0}^{n-1}\sum_{j=0}^{\lfloor \frac{k\cdot\mathrm{gcd}(i,n)}{n}\rfloor} g(\mathrm{gcd}(n,i),j) \]考慮怎麼求 \(g(n,m)\)
假設我們已經在 \(m\) 個不相鄰的點塗了綠色,在綠色點的間隙中,要麼"紅藍紅...",要麼"藍紅藍..."這樣塗色,一共有 \(m\) 個間隙,如果已經確定了綠色的塗色方案,也即確定了每個間隙的長度,每個間隙中只有兩種塗其他顏色的方案,那麼塗其他顏色的方案數為 \(2^m\),還要乘上一個塗綠色點的方案數,即為 \(g(n,m)\)。而塗綠色點的方案數等於在長度為 \(n\) 的圓環上選取 \(m\) 個不相鄰的點的方案數,若 \(n\) 號點已經被塗成綠色,則剩下 \(n-m-1\) 個位置要插入 \(m-1\)
再考慮答案的式子,先列舉 \(\mathrm{gcd}\),有
\[ans=\frac{1}{n}\sum_{i=0}^{n-1}\sum_{j=0}^{\lfloor \frac{k\cdot\mathrm{gcd}(i,n)}{n}\rfloor} g(\mathrm{gcd}(n,i),j)\\ =\frac{1}{n}\sum_{d|n}\sum_{i=0}^{n-1}[\mathrm{gcd}(n,i)=d]\sum_{j=0}^{\lfloor \frac{k\cdot d}{n}\rfloor}g(d,j)\\ =\frac{1}{n}\sum_{d|n}\sum_{i=1}^{n}[\mathrm{gcd}(n,i)=d]\sum_{j=0}^{\lfloor \frac{k\cdot d}{n}\rfloor}g(d,j)\\ =\frac{1}{n}\sum_{d|n}\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}[\mathrm{gcd}(\lfloor\frac{n}{d}\rfloor,i)=1]\sum_{j=0}^{\lfloor \frac{k\cdot d}{n}\rfloor}g(d,j)\\ =\frac{1}{n}\sum_{d|n}\varphi(\lfloor\frac{n}{d}\rfloor)\sum_{j=0}^{\lfloor \frac{k\cdot d}{n}\rfloor}g(d,j)\\ =\frac{1}{n}\sum_{d|n}\varphi(\lfloor\frac{n}{d}\rfloor)F(d) \]其中
\[F(d)=\sum_{j=0}^{\lfloor \frac{k\cdot d}{n}\rfloor}g(d,j) \]我們可以預處理出階乘和階乘的逆元來快速計算組合數。對於每一個 \(n\) 的因子 \(d\),計算出 \(F(d)\),然後累加即可。
Code
#include <bits/stdc++.h>
using namespace std;
#define RG register int
#define LL long long
const LL MOD = 998244353LL;
LL inv[1000010], fact[1000010], finv[1000010], two[1000010];
int Phi[1000005];
bool not_Prime[1000005];
vector<int> Prime;
void Init() {
inv[1] = fact[0] = fact[1] = finv[0] = finv[1] = two[0] = 1;
two[1] = 2;
for (int i = 2;i <= 1000000;++i) {
inv[i] = ((-(MOD / i) * inv[MOD % i]) % MOD + MOD) % MOD;
fact[i] = fact[i - 1] * i % MOD;
finv[i] = finv[i - 1] * inv[i] % MOD;
two[i] = (two[i - 1] << 1) % MOD;
}
}
LL C(int n, int m) {
if (m > n) return 0;
return fact[n] * finv[m] % MOD * finv[n - m] % MOD;
}
void Get_Phi(int Len) {
not_Prime[1] = true;
Phi[1] = 1;
int Count = 0;
for (int i = 2;i <= Len;i++) {
if (!not_Prime[i]) { Prime.push_back(i);Phi[i] = i - 1; }
for (int j = 0;j < Prime.size();++j) {
int mid = i * Prime[j];
if (mid > Len) break;
not_Prime[mid] = true;
if (i % Prime[j] == 0) { Phi[mid] = Phi[i] * Prime[j];break; }
Phi[mid] = Phi[i] * (Prime[j] - 1);
}
}
}
int T, n, k;
inline LL g(int d, int i) {
if (i == 0) {
if (d & 1) return 0;
return 2;
}
return two[i] * (C(d - i, i) + C(d - i - 1, i - 1)) % MOD;
}
LL F(int d) {
LL res = 0;
for (int i = 0;i <= (1LL * k * d) / n;++i)
res = (res + g(d, i)) % MOD;
return res;
}
int main() {
Init();
Get_Phi(1000000);
cin >> T;
while (T--) {
cin >> n >> k;
int d;LL ans = 0;
for (d = 1;d * d < n;++d) {
if (n % d == 0) {
ans = (ans + Phi[n / d] * F(d) % MOD) % MOD;
ans = (ans + Phi[d] * F(n / d) % MOD) % MOD;
}
}
if (d * d == n) ans = (ans + Phi[d] * F(d) % MOD) % MOD;
ans = ans * inv[n] % MOD;
printf("%lld\n", ans);
}
return 0;
}