传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=1815
思路:这道题用来polya计数的入门。。。让我稍微理解了一下polya定理
这题的思路还是非常好的,首先我们考虑同构计数,我们是无法直接套用polya计数的,因为这个题有一个很蛋疼的地方就是点的置换造成边的置换,那么我们只能套用burnside,注意到这里一共有
n!
个置换,总的置换集相当于全排列,那么这样不动点等价于:若
c(u,v)=k[c表示颜色]
,那么
c(f(u),f(v))=k[f表示置换]
.
根据这个性质,我们可以开始着手问题了,在xiaoyimi大爷的提示下,我们发现一个很经典的数量级,注意到很多解的形式是相同的,我们按循环节大小排序,那么这个合法解的数量级是很小的,在
n=53
的时候不过
3∗105
,
n=60
的时候才
106
左右,我认为这是不好注意到的一点性质。
那么我们有一种方法,我们找出每种有序的置换,然后暴力
O(n2)
计算其贡献,但这样做是会
T
掉的。
那么我们注意到对于两个不同的循环节(设大小分别是
另外对于同一循环节内的边,什么时候会相同呢?按照刚才的思路分析会发现实际上这样相同的边是对称的,这样来看不同的颜色数是
⌊n2⌋
,但这一步也是可以暴力
O(n3)
计算的。
然后我们的目标变为求两两gcd,在本题中因为并不是每次循环节个数都达到
n
,所以可以直接预处理gcd后暴力,然而这样最坏复杂度是
这玩意当然是可以优化成
O(nlogn)
的了,我们用
ϕ
函数加速计算,利用这个性质:
代码:
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<string>
#define N 55
#define int long long
using namespace std;
int prime[N],phi[N],mo,n,m,cnt,ans,da[65],a[65],b[65][65],c[1775],fac[1775],inv[1775],num[N],haha,kkk[N],poq[N][N];
bool vis[1775],not_prime[N];
void Sieve(){
memset(not_prime,0,sizeof(not_prime));
cnt = 0;
phi[1]=1;
for (int i = 2;i < N - 1; ++i){
if (!not_prime[i]) prime[++cnt] = i,phi[i] = i - 1;
for (int j = 1;j <= cnt; ++j)
if (prime[j] * i >= N - 1) break;
else {
not_prime[prime[j] * i] = 1;
if (i % prime[j]) phi[i * prime[j]] = phi[i] * (prime[j] - 1);
else{
phi[i * prime[j]] = phi[i] * prime[j];
break; }
}
}
int x = 0,y = 0;
memset(kkk,0,sizeof(kkk));
for (int s = 1;s < N - 1; ++s){
memset(poq,0,sizeof(poq));
x = 0; y = 0;
for (int i = 0;i < s; ++i)
for (int j = i + 1;j < s; ++j)
if (!poq[i][j]) {
++x;
int aa,bb;
for (aa = i,bb = j;!poq[aa][bb];aa = (aa + 1) % s,bb = (bb + 1) % s) poq[aa][bb] = poq[bb][aa] = x;
if (poq[aa][bb] == x) ++y;
}
kkk[s] = y;
}
//for (int i = 1;i <= 5; ++i) cout<<kkk[i]<<" ";
}
int qr(int x,int y)
{
int t=1;
for (;y;y>>=1,x=x*x%mo)
if (y&1) t=t*x%mo;
return t;
}
void dfs(int sum,int last)
{
if (sum==n)
{
int t=0,fi=1,last=1,sum=fac[n];
for (int len,i=1;i<=da[0];i++)
{
fi=last;
sum=sum*inv[da[i]]%mo*fac[da[i]-1]%mo;
for (int j=1;j<=da[i];++j)
a[last]=last+1,
++last;
--last;
a[last]=fi;
++last;
}
for (int len,i=1;i<=da[0];i=len+1)
{
len=i;
while (len<da[0]&&da[len]==da[len+1]) ++len;
sum=sum*inv[len-i+1]%mo;
}
/*for (int i=1;i<=n;++i)
for (int j=i+1;j<=n;++j)
c[b[i][j]]=b[a[i]][a[j]]; */
/*for (int i=1;i<=cnt;++i) vis[i]=0;*/
int fpp = 0;
for (int k = 1;k < N - 1; ++k)
{ int fp = 0;
for (int j = k;j < N - 1;j += k) fp += num[j];
fpp += ((phi[k] * fp * fp));
//printf("%d %d\n",fp,fpp);
}
fpp = (fpp - n) / 2 + haha;
/*for (int i=1;i<=cnt;++i)
if (!vis[i])
{
++t;
for (int j=i;!vis[j];j=c[j])
vis[j]=1;
}
printf("%d:",t);
for (int i=1;i<=da[0];++i) printf("%d ",da[i]);
puts("");*/
(ans+=sum*qr(m,fpp)%mo)%=mo;
return;
}
for (int i=last;sum+i<=n;++i)
num[i]++,
haha += kkk[i],
da[++da[0]] = i,
dfs(sum+i,i),
--num[i],
--da[0],
haha -= kkk[i];
}
main()
{
Sieve();
haha = 0;
memset(num,0,sizeof(num));
scanf("%d%d%d",&n,&m,&mo);
// freopen("std.txt","w",stdout);
fac[0]=1;
for (int i=1;i<=1770;++i) fac[i]=fac[i-1]*i%mo;
inv[1]=1;
for (int i=2;i<=1770;++i) inv[i]=(mo-mo/i)*inv[mo%i]%mo;
for (int i=2;i<=1770;++i) inv[i]=inv[i]*inv[i-1]%mo;
for (int i=1;i<=n;++i)
for (int j=i+1;j<=n;++j) b[i][j]=b[j][i]=++cnt;
dfs(0,1);
// sort(fp+1,fp+fp[0]+1);
printf("%d\n",ans*inv[n]%mo);
// for (int i=1;i<=fp[0];++i) printf("%d\n",fp[i]);
// }
}
总结: