题解 P4128 【[SHOI2006]有色图】
P4128 [SHOI2006]有色图
这题想起来挺难的。。。
至少我这么认为,直到看了某巨佬的博客。
首先它是边的置换。
但是我们的
所以我们要看一下点置换和边置换之间的关系。
假定一个点置换是
-
对于不在一个循环里面的点:
比如
a_1,b_1 , 那么会有边循环((a1,b1),(a2,b2)...) 设
a_i 循环的循环节是l_1 ,b_i 循环的循环节是l_2 ,那么形成的边循环的循环节显然是lcm(l_1,l_2) 。一共有
l_1* l_2 个点对,每个点对都在一个循环节为lcm(l_1,l_2) 的循环上,所以一共有\frac{l_1* l_2}{lcm(l_1,l_2)}=gcd(l_1,l_2) 个循环节,所以C(f)=m^{gcd(l_1,l_2)} 。(回到
Burnside 引理,C 为置换之后仍为自身的数目,就是说要循环节里的每条边都一样的颜色) -
对于在一个循环里面的点:
比如
a_1,a_2 。设
a_i 的循环节为l_1 。如果
l_1 是奇数,那么循环长度为l_1 ,一共有\left(\begin{array}{c}l_1\\2\end{array}\right) 个点对,所以是\frac{l_1-1}{2} 个循环节,所以C(f)=m^{\frac{l_1-1}{2}} 。如果
l_1 是偶数,除了上面这种情况之外,还有一种的循环节是\frac{l_1}{2} (就是两个点刚好相隔半个周期,而边是双向的),所以一共有\frac{\left(\begin{array}{c}l_1\\2\end{array}\right)-\frac{l_1}{2}}{l1}+1=\frac{l_1}{2} 个点对。
综上,边的置换的循环数为:
实际
若将一个循环看成一个圆形的排列,现在要把
因为存在
则总分配数为:
然后我们通过搜索,确定点的置换长度
于是我们所求的答案就解决了:
所以代码很简单,只要枚举
但是又有除,又有模,怎么办?
套路求逆元,注意到
附代码:
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cmath>
#define MAXN 70
using namespace std;
long long n,m,p,ans=0,fact[MAXN],num[MAXN];//直接开long long
inline long long read(){//快读
long long date=0,w=1;char c=0;
while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();}
return date*w;
}
long long gcd(long long x,long long y){//这个gcd应该都会吧
if(!y)return x;
return gcd(y,x%y);
}
long long mexp(long long a,long long b,long long c){//快速幂也应该会吧
long long s=1;
while(b){
if(b&1)s=s*a%c;
a=a*a%c;
b>>=1;
}
return s;
}
void get_answer(int x){//累计答案
int top=1;
long long sum=0,mul=1;
for(int i=1;i<=x;i++)sum+=num[i]/2;
for(int i=1;i<=x;i++)
for(int j=i+1;j<=x;j++)
sum+=gcd(num[i],num[j]);
for(int i=1;i<=x;i++)mul=(mul*num[i])%p;
for(int i=2;i<=x;i++){
if(num[i]!=num[i-1]){
mul=(mul*fact[top])%p;
top=0;
}
top++;
}
mul=(mul*fact[top])%p;
mul=fact[n]*mexp(mul,p-2,p)%p;
ans=(ans+mul*mexp(m,sum,p)%p)%p;//记得每次都要累计
}
void dfs(int x,int h,int s){//直接暴搜
if(!h)get_answer(x-1);
if(h<s)return;
for(int i=s;i<=h;i++){
num[x]=i;
dfs(x+1,h-i,i);
num[x]=0;
}
}
void work(){
dfs(1,n,1);
ans=ans*mexp(fact[n],p-2,p)%p;
printf("%lld\n",ans);
}
void init(){//读入+预处理阶乘
n=read();m=read();p=read();
fact[0]=1;
for(int i=1;i<=n;i++)fact[i]=(fact[i-1]*i)%p;
}
int main(){//主函数So easy!
init();
work();
return 0;
}