[SHOI2006] 有色图

tag:polya,组合计数


很显然要用到polya。

此题中的变换可以理解为,枚举一个排列 \(p\),把 \(i\) 变成 \(p_i\)。

那么根据polya,有

\[ans=\sum_{p}m^\text{等价类个数} \]

那么此题中的等价类是什么意思呢。

注意到我们要求的是对边进行染色的方案,所以这里的等价关系指的就是 \((u,v)\) 和 \((p_u,p_v)\)。


观察数据范围,根据常用套路,我们考虑将置换拆成若干个循环,那么一对长度为 \(a,b\) 的循环能够贡献多少个等价类呢?

以 \(2,3\) 为例子

\[\begin{matrix}1&2&3&1&2&3\\1&2&1&2&1&2\end{matrix} \]

可以发现一个等价类的大小为 \(lcm(a,b)\),而一共有 \(ab\) 条边,所以等价类就有 \(\frac{ab}{lcm(ab)}=\gcd(a,b)\) 个。

然后考虑一个循环内部能贡献几个等价类。

以 \(5\) 为例子

\[\begin{matrix}1&2&3&4&5\\2&3&4&5&1\end{matrix} \]

\[\begin{matrix}1&2&3&4&5\\3&4&5&1&2\end{matrix} \]

注意到每个等价类会把 \((1,i+1)\) 和 \((1,n-i+1)\) 归为一类,所以实际上就有 \(\lceil\frac{n-1}2\rceil\) 个等价类。


实现的时候 \(53\) 的拆分数是 \(329931\),所以暴力枚举拆分就好了。

然后分 \(3\) 种情况:

  • 长度不同的组:\(cnt_i\cdot cnt_j\cdot\gcd(a_i,a_j)\)
  • 长度相同的组:\(\binom{cnt_i}2a_i\)
  • 组内贡献:\(cnt_i\lceil\frac{a_i-1}2\rceil\)

然后还要乘上这种拆分对应的方案数:

先选出每组的数,然后乘上组内方案,同长度的组要去重。

\[\frac{n!}{\prod(len_i!)}\cdot\prod(len_i-1)\cdot\prod\frac1{cnt_i!}=\frac{n!}{\prod len_i\prod cnt_i!} \]

\(cnt_i\) 表示长度为 \(i\) 的循环有 \(cnt_i\) 个。

由于最后要除以 \(n!\),所以这里的 \(n!\) 可以扔掉。


#include<bits/stdc++.h>
using namespace std;
   
template<typename T>
inline void Read(T &n){
    char ch; bool flag=false;
    while(!isdigit(ch=getchar()))if(ch=='-')flag=true;
    for(n=ch^48;isdigit(ch=getchar());n=(n<<1)+(n<<3)+(ch^48));
    if(flag)n=-n;
}

int n, m, p;

inline int ksm(int base, int k=p-2){
    int res=1;
    while(k){
        if(k&1)
            res = 1ll*res*base%p;
        base = 1ll*base*base%p;
        k >>= 1;
    }
    return res;
}

int a[54], cnt[54], top, jc[54], invjc[54], inv[54];
int gcd[54][54], ans;

inline void solve(){
    int res=0;
    for(int i=1; i<=top; i++) for(int j=i+1; j<=top; j++) res += cnt[i]*cnt[j]*gcd[a[i]][a[j]];
    for(int i=1; i<=top; i++) res += cnt[i]*(cnt[i]-1)/2*a[i];
    for(int i=1; i<=top; i++) res += cnt[i]*(a[i]/2);
    res = ksm(m,res);
    int tmp=1;
    for(int i=1; i<=top; i++) tmp = 1ll*tmp*invjc[cnt[i]]%p*ksm(inv[a[i]],cnt[i])%p;
    ans = (ans+1ll*res*tmp)%p;
}

void dfs(int rem, int prv){
    if(!rem) return solve();
    if(prv and (rem-prv>=prv or rem==prv)) cnt[top]++, dfs(rem-prv,prv), cnt[top]--;
    top++;
    for(int i=prv+1; i<=rem-i; i++){
        a[top] = i; cnt[top] = 1;
        dfs(rem-i,i);
    }
    if(rem>prv) a[top] = rem, cnt[top] = 1, dfs(0,rem);
    a[top] = 0; cnt[top] = 0; top--;
}

int Gcd(int a, int b){return b?Gcd(b,a%b):a;}

int main(){
    Read(n); Read(m); Read(p);
    for(int i=1; i<=n; i++) for(int j=1; j<=n; j++) gcd[i][j] = Gcd(i,j);
    for(int i=1; i<=n; i++) inv[i] = ksm(i);
    jc[0] = 1; for(int i=1; i<=n; i++) jc[i] = 1ll*jc[i-1]*i%p;
    invjc[n] = ksm(jc[n]); for(int i=n; i; i--) invjc[i-1] = 1ll*invjc[i]*i%p;
    dfs(n,0);
    cout<<ans<<'\n';
    return 0;
}
上一篇:pythonchallenge闯关——第五弹


下一篇:Python challenge闯关答案(更新中)