BZOJ[1815]男人一上午寫一道題,機房人不解;看到內容,衆人沉默,網友:長見識了

傳送門ber~

詳細題解

知道點的順序,邊的順序也能確定下來,所以對於點置換可以算出相應的邊置換計算
可我們總不能n!n!枚舉全排列,我們可以進一步發現,結構相同的點置換對應的邊置換肯定是一樣的(廢話),可以搜出所有不同結構的點置換,發現n=53n=53時點置換個數不到30w30w

當前搜到點置換L1L2L3...LkL_1\le L_2\le L_3\le...\le L_k,那麼需要計算邊置換循環個數
連接不同點循環循環i,j的邊:一個循環覆蓋Lcm(Li,Lj)Lcm(L_i,L_j)條邊,所以c=LiLjLcm(Li,Lj)=gcd(Li,Lj)c=\frac{L_i*L_j}{Lcm(L_i,L_j)}=gcd(L_i,L_j)個循環(畫畫圖更容易理解一點)
點循環ii內部的邊:循環數爲Li12\frac{L_i-1}{2}(因爲一半會重)
所以總循環數C=Li2+gcd(Li,Lj)C=\sum \lfloor \frac{L_i}{2} \rfloor+\sum\sum gcd(L_i,L_j)
L1L2L3...LkL_1\le L_2\le L_3\le...\le L_k循環數量容易推得爲n!L1L2...LnB1!B2!...Bn!\frac{n!}{L_1L_2...L_nB_1!B_2!...B_n!},其中BiB_i表示循環長度爲ii的循環個數
剩下的套Polya就行了

代碼如下:

#include<bits/stdc++.h>
#define int long long
#define N 120
using namespace std;
inline int read(){
    int x=0,f=1;char c;
    do c=getchar(),f=c=='-'?-1:f; while(!isdigit(c));
    do x=(x<<3)+(x<<1)+c-'0',c=getchar(); while(isdigit(c));
    return x*f;
}
typedef long long LL;
int n,m,p,s[N],a[N],jc[N],gd[N][N];
LL ans;
LL gcd(LL a,LL b){
    return b?gcd(b,a%b):a;
}
inline LL qpow(LL x,int k){
    LL sum=1;
    while(k){
        if(k&1) sum=sum*x%p;
        x=x*x%p;
        k>>=1;
    }
    return sum;
}
void dfs(int x,int cnt,int pre){
    if(cnt==n){
        LL t=0,pp=1;
        for(int i=1;i<x;i++)
            t+=s[i]/2;
        for(int i=1;i<x;i++)
            for(int j=i+1;j<x;j++)
                t+=gd[s[i]][s[j]];
        for(int i=1;i<=n;i++) (pp*=jc[a[i]])%=p;
        for(int i=1;i<x;i++) (pp*=s[i])%=p;
       // cout<<pp<<" "<<t<<endl;
        (ans=ans+jc[n]*qpow(pp,p-2)%p*qpow(m,t)%p)%=p;
        return;
    }
    for(int i=pre;i<=n-cnt;i++){
        s[x]=i;a[i]++;
        dfs(x+1,cnt+i,i);
        a[i]--;
    }
}
main(){
    n=read();m=read();p=read();
    for(int i=jc[0]=1;i<=n;i++) jc[i]=jc[i-1]*i%p;
    for(int i=1;i<=n;i++)
        for(int j=i;j<=n;j++) gd[i][j]=gd[j][i]=gcd(i,j);
    dfs(1,0,1);
    printf("%lld\n",ans*qpow(jc[n],p-2)%p);
    return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章