欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

某 SCOI 模拟赛 T3 画图(draw)(无根无标号平面树计数)

程序员文章站 2023-12-25 12:42:15
...

题意

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

题解

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

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

  • 不旋转C(n)C(n)CC 指卡特兰数)(卡特兰数的经典性质);
  • nn 是奇数时旋转 180180^\circnn 格):此时一定有一条直径,直径两侧的连法中心对称,不动点个数为 nC(n12)n\cdot C({n-1\over 2})
  • 下面简要证明其他情况下只能旋转偶数格:
    某 SCOI 模拟赛 T3 画图(draw)(无根无标号平面树计数)
    给圆上每个点依次编号,显然每条边起点与终点的奇偶性不同。为了使旋转过后能够重合,原来是起点的点现在仍然该是起点,那么其奇偶性应当不变,所以只能旋转偶数格。(但是直径在旋转中不受影响,因此旋转 180180^\circ 的情况需要特殊考虑);
  • 旋转了 2m2m 格(1m<n1\leq m<n:记 gcd(m,n)=d\gcd(m,n)=d,则每 2d2d 格的连法应当是重复的。在 2d2d 格中任选 dd 个点作为起点,不动点个数为 (2dd)2d\choose d。(官方题解用生成函数推的,似乎比较麻烦;不过考试时把这个结论像这样瞪出来也不太容易)
    下图解释了为何任选 dd 个点作为起点都是可行的。
    某 SCOI 模拟赛 T3 画图(draw)(无根无标号平面树计数)
    这一部分(旋转 2m2m 格)的总的不动点个数为:
    m=1n1(2gcd(m,n)gcd(m,n))=dn,dn(2dd)m=1nd[gcd(m,nd)=1]=dn,dn(2dd)φ(nd)\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}

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

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

#include<bits/stdc++.h>
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<<"> "<<i<<" "<<c[i]<<" "<<cat[i]<<endl;
    }
}
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<<endl;
        ans=ans*1ll*getinv(2*n,p)%p;
        cout<<ans<<endl;
    }
    return 0;
}

上一篇:

下一篇: