某 SCOI 模拟赛 T3 画图(draw)(无根无标号平面树计数)【卡特兰数 Burnside引理】

题意

平面上有 n n n 个点, n − 1 n-1 n1 条只在各自端点处相交的线段将它们连成一棵树。如果两颗树能通过移动顶点位置重合且移动过程中线段只在各自端点处相交,两棵树等价。问:有多少棵两两不等价的有 n n n 个点的树,答案模给定的质数 p p p。多组询问。 n ≤ 5 × 1 0 5 n\leq 5\times 10^5 n5×105 1 0 8 ≤ p ≤ 1 0 9 10^8\leq p\leq 10^9 108p109 T ≤ 3 T\leq 3 T3

题解

首先每一棵顶点数为 n + 1 n+1 n+1 的树可以转化为:圆上有等距的 2 n 2n 2n 个点,选出 n n n 对点,每一对点之间连一条边,边之间不相交的方案数(旋转同构)。

下面我们称相邻两点之间的距离为“1 格”。
某 SCOI 模拟赛 T3 画图(draw)(无根无标号平面树计数)【卡特兰数 Burnside引理】_第1张图片
于是考虑 Burnside 引理,考虑所有置换的不动点个数:

  • 不旋转 C ( n ) C(n) C(n) C C C 指卡特兰数)(卡特兰数的经典性质);
  • n n n 是奇数时旋转 18 0 ∘ 180^\circ 180 n n n 格):此时一定有一条直径,直径两侧的连法中心对称,不动点个数为 n ⋅ C ( n − 1 2 ) n\cdot C({n-1\over 2}) nC(2n1)
  • 下面简要证明其他情况下只能旋转偶数格:
    某 SCOI 模拟赛 T3 画图(draw)(无根无标号平面树计数)【卡特兰数 Burnside引理】_第2张图片
    给圆上每个点依次编号,显然每条边起点与终点的奇偶性不同。为了使旋转过后能够重合,原来是起点的点现在仍然该是起点,那么其奇偶性应当不变,所以只能旋转偶数格。(但是直径在旋转中不受影响,因此旋转 18 0 ∘ 180^\circ 180 的情况需要特殊考虑);
  • 旋转了 2 m 2m 2m 格( 1 ≤ m < n 1\leq m1m<n:记 gcd ⁡ ( m , n ) = d \gcd(m,n)=d gcd(m,n)=d,则每 2 d 2d 2d 格的连法应当是重复的。在 2 d 2d 2d 格中任选 d d d 个点作为起点,不动点个数为 ( 2 d d ) 2d\choose d (d2d)。(官方题解用生成函数推的,似乎比较麻烦;不过考试时把这个结论像这样瞪出来也不太容易)
    下图解释了为何任选 d d d 个点作为起点都是可行的。
    某 SCOI 模拟赛 T3 画图(draw)(无根无标号平面树计数)【卡特兰数 Burnside引理】_第3张图片
    这一部分(旋转 2 m 2m 2m 格)的总的不动点个数为:
    ∑ m = 1 n − 1 ( 2 gcd ⁡ ( m , n ) gcd ⁡ ( m , n ) ) = ∑ d ∣ n , d ≠ n ( 2 d d ) ∑ m ′ = 1 n d [ gcd ⁡ ( m , n d ) = 1 ] = ∑ d ∣ n , d ≠ n ( 2 d d ) φ ( n d ) \begin{aligned} &\sum\limits_{m=1}^{n-1}{2\gcd(m,n)\choose\gcd(m,n)}\\ =&\sum\limits_{d\mid n,d\not= n}{2d\choose d}\sum\limits_{m'=1}^{n\over d}[\gcd(m,{n\over d})=1]\\ =&\sum\limits_{d\mid n,d\not=n}{2d\choose d}\varphi({n \over d}) \end{aligned} ==m=1n1(gcd(m,n)2gcd(m,n))dn,d=n(d2d)m=1dn[gcd(m,dn)=1]dn,d=n(d2d)φ(dn)

给以上各种情况的不动点个数加起来除以 2 n 2n 2n 即可得到答案。

代码(似乎算了许多没必要的东西,不过不重要了):

#include
using namespace std;
int getint(){
    int ans=0;
    char c=getchar();
    while(c<'0'||c>'9')c=getchar();
    while(c>='0'&&c<='9'){
        ans=ans*10+c-'0';
        c=getchar();
    }
    return ans;
}
const int N=5e5+10;
bool boo[N];
int pri[N],cnt=0,phi[N];
int cat[N],c[N],frac[N<<1],ifrac[N<<1];//c[i]=C(i,2i)=cat[i]*(i+1)
int getinv(int x,int p){
    int y=p-2,ans=1;
    while(y){
        if(y&1)ans=ans*1ll*x%p;
        x=x*1ll*x%p;
        y>>=1;
    }
    return ans;
}
void getcat(int n,int p){
    for(int i=frac[0]=1;i<=n*2;i++)frac[i]=frac[i-1]*1ll*i%p;
    ifrac[n*2]=getinv(frac[n*2],p);
    for(int i=n*2-1;i>=0;--i)ifrac[i]=ifrac[i+1]*(i+1ll)%p;
    assert(ifrac[0]==1);
    for(int i=1;i<=n;i++){
        c[i]=frac[i*2]*1ll*ifrac[i]%p*1ll*ifrac[i]%p;
        cat[i]=c[i]*1ll*getinv(i+1,p)%p;
        //cerr<<"> "<
    }
}
void getphi(){
    boo[1]=phi[1]=1;
    for(int i=2;i<N;i++){
        if(!boo[i]){
            pri[cnt++]=i;
            phi[i]=i-1;
        }
        for(int j=0;j<cnt&&pri[j]*i<N;j++){
            boo[pri[j]*i]=1;
            if(i%pri[j]){
                phi[pri[j]*i]=phi[pri[j]]*phi[i];
            }else{
                phi[pri[j]*i]=pri[j]*phi[i];
                break;
            }
        }
    }
}

int main(){
    getphi();
    int T=getint();
    while(T--){
        int n=getint(),p=getint();
        getcat(n,p);--n;
        int ans=0;
        ans+=cat[n];
        if(n&1)ans+=cat[n/2]*1ll*n%p;
        ans%=p;
        for(int d=1;d*d<=n;d++){
            if(n%d)continue;
            ans=(ans+c[d]*1ll*phi[n/d])%p;
            if(d!=1&&d*d!=n)ans=(ans+c[n/d]*1ll*phi[d])%p;
        }
        //cerr<<"? "<
        ans=ans*1ll*getinv(2*n,p)%p;
        cout<<ans<<endl;
    }
    return 0;
}

你可能感兴趣的:(题解,#,来源-模拟赛,#)