题解 poj 2154 Color

传送门


【题意】

\(x\) 组数据

每组询问 \(n\) 个颜色填涂 \(n\) 个空位的环,问不同构方案数,答案对 \(p\) 取模。

规定某个环能被另一个环旋转得到时,这两个环同构。

\(x\leq 3500, n\leq 10^9\)


【分析】

显然 \(n\) 个角度的旋转构成一个群,根据 polya 定理,得到答案为:

\(\displaystyle {1\over n}\sum_{i=1}^n n^{\gcd(i, n)}={1\over n}\sum_{d\mid n} n^d \boldsymbol \varphi({n\over d})=\sum_{d\mid n} n^{d-1} \boldsymbol \varphi({n\over d})\)

但如果暴力计算每一个 \(\boldsymbol \varphi({n\over d})\) 复杂度为 \(O(n^{3\over 4})\) 可能不够,考虑先打出前 \({n^{3\over 5}}\) 个欧拉函数值,则求解所有欧拉函数的复杂度降低为 \(O(n^{3\over 5})\)(证明如下)

故求解一组的复杂度为 \(O(n^{3\over 5})+O(\sqrt n)+O(\sqrt n\log n)=O(n^{3\over 5})\),总复杂度即为 \(O(x\cdot n^{3\over 5})\),卡卡常能过


【代码】

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll, ll> pii;
typedef double db;
#define fi first
#define se second
int n, mod;
inline int fpow(int a,int x) { ll ans=1; for(;x;x>>=1,a=(ll)a*a%mod) if(x&1) ans=(ll)ans*a%mod; return ans; }
const int Lim=2.6e5, MAXN=Lim+10;
int fc[MAXN], phi[MAXN], prime[MAXN], cntprime;
inline int getphi(int n) {
    if(n<=Lim) return phi[n];
    int val=n;
    for(int j=1;j<=cntprime;++j)
        if((ll)prime[j]*prime[j]>n) break;
        else if(n%prime[j]==0){
            val=val/prime[j]*(prime[j]-1);
            while(n%prime[j]==0) n/=prime[j];
        }
    if(n!=1) val=val/n*(n-1);
    return val;
}
inline int ans(){
    int res=0;
    for(int i=1;i*i<=n;++i) if(n%i==0) {
        int j=n/i;
        res+=(ll)getphi(j)*fpow(n, i-1)%mod;
        res-=(res>=mod?mod:0);
        if(i!=j){
            res+=(ll)getphi(i)*fpow(n, j-1)%mod;
            res-=(res>=mod?mod:0);
        }
    }
    return res;
}
inline void sieve(){
    phi[1]=1;
    for(int i=2;i<=Lim;++i){
        if(!fc[i]) fc[i]=prime[++cntprime]=i, phi[i]=i-1;
        for(int j=1;j<=cntprime;++j)
            if(prime[j]*i>Lim||prime[j]>fc[i]) break;
            else fc[prime[j]*i]=prime[j], phi[prime[j]*i]=phi[i]*(prime[j]-(prime[j]!=fc[i]));
    }
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    sieve();
    int x; cin>>x;
    while(x--&&cin>>n>>mod) cout<<ans()<<"\n";
    cout.flush();
    return 0;
}

【证明】

朴素求解所有欧拉函数的复杂度求解如下考虑:

由于每个数字 \(n\) ,设其存在一个严格小于 \(\sqrt n\) 的因数 \(d\) ,则必然同时存在一严格大于 \(\sqrt n\) 的因数 \({n\over d}\)

故因数个数至多为 \(2\cdot(\sqrt n-1)+1=2\sqrt n-1\)

我们朴素求解 \(n\) 的欧拉函数值,复杂度为 \(O(\sqrt n)\)

\(\displaystyle T(n)=\sum_{d\mid n}C\cdot \sqrt d\leq C\sum_{d=1}^{\sqrt n}(\sqrt d+\sqrt {n\over d})\)

由于 \(\displaystyle \sum_{x=1}^{\sqrt n} \sqrt x\approx \int_1^{\sqrt n}x^{1\over 2}\text dx={2\over 3}(x^{3\over 2}|^{\sqrt n}_1)=O(n^{3\over 4})\)

\(\displaystyle \sum_{x=1}^{\sqrt n} {1\over \sqrt x}\approx \int_1^{\sqrt n} x^{-{1\over 2}}\text dx=2(x^{1\over 2}|^{\sqrt n}_1)=O(n^{1\over 4})\)

\(T(n)=C\cdot O(n^{3\over 4})+C\cdot \sqrt n\cdot O(n^{1\over 4})=O(n^{3\over 4})\)

此为朴素计算的复杂度

考虑有限筛出 \(m\) 范围内欧拉函数的值,由上述朴素的分析得到:

\(m<\sqrt n\) ,则前半部分时间复杂度减小,后半部分不变,总复杂度为 \(O(m)+o(n^{3\over 4})+O(n^{3\over 4})=O(n^{3\over 4})\)

\(m\geq \sqrt m\),此时可得,后半部分的因数均大于 \(m\),故其对应的小因数均小于 \({n\over m}\)

故复杂度化为 \(\displaystyle T(n)=O(m)+\sum_{i=1}^{n\over m}C\cdot \sqrt{n\over i}=O(m)+C\cdot \sqrt n\cdot O(({n\over m})^{1\over 4})=O(m)+O({n^{3\over 4}\over m^{1\over 4}})\)

\(\displaystyle T(n)=C_1m+C_2\cdot {n^{3\over 4}\over m^{1\over 4}}\)

根据均值不等式,\(\displaystyle C_1m=C_2\cdot {n^{3\over 4}\over m^{1\over 4}}\)\(T(n)\) 取最小值,达到 \(O(m)\)

求解等式得到 \(m=({C_2\over C_1})^{4\over 5}\cdot n^{3\over 5}\)

由于我也不知道 \(C_1, C_2\) 的值,故均取 \(1\),得到 \(T(n)=O(m)=O(n^{3\over 5})\)

posted @ 2021-07-10 17:03  JustinRochester  阅读(38)  评论(0编辑  收藏  举报