1. 程式人生 > 其它 >[Burnside引理+數論] HDU 6960 Necklace of Beads

[Burnside引理+數論] HDU 6960 Necklace of Beads

題目大意

有一個由 \(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)\)

段,每一段的長度均為 \(\frac{n}{\mathrm{gcd}(n,i)}\),每一段內的點顏色必須相同,只有這樣,順時針旋轉 \(i\) 步後圓環才和初始時相同。所以此時要麼整個段全為綠色,要麼整個段全不為綠色。那麼順時針旋轉 \(i\) 步時和原來相同的染色方案數為

\[\sum_{j=0}^{\lfloor \frac{k\cdot\mathrm{gcd}(i,n)}{n}\rfloor} g(\mathrm{gcd}(n,i),j) \]

而對於一個 \(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)\)

,即長度為 \(n\) 的圓環,有 \(m\) 個點是綠色,不考慮本質不同的塗色方案數。

假設我們已經在 \(m\) 個不相鄰的點塗了綠色,在綠色點的間隙中,要麼"紅藍紅...",要麼"藍紅藍..."這樣塗色,一共有 \(m\) 個間隙,如果已經確定了綠色的塗色方案,也即確定了每個間隙的長度,每個間隙中只有兩種塗其他顏色的方案,那麼塗其他顏色的方案數為 \(2^m\),還要乘上一個塗綠色點的方案數,即為 \(g(n,m)\)。而塗綠色點的方案數等於在長度為 \(n\) 的圓環上選取 \(m\) 個不相鄰的點的方案數,若 \(n\) 號點已經被塗成綠色,則剩下 \(n-m-1\) 個位置要插入 \(m-1\)

個綠色點,方案數為 \(\binom{n-m-1}{m-1}\);若不把 \(n\) 號點塗成綠色,則剩下 \(n-m\) 個位置要插入 \(m\) 個綠色點,方案數為 \(\binom{n-m}{m}\)。所以 \(g(n,m)=2^m\left[\binom{n-m}{m}+\binom{n-m-1}{m-1}\right],m\neq 0\)。當 \(m=0\) 時,若 \(n\) 為奇數,\(g(n,0)=0\),若 \(n\) 為偶數,\(g(n,0)=2\)

再考慮答案的式子,先列舉 \(\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;
}