码迷,mamicode.com
首页 > 其他好文 > 详细

项链(burnside)

时间:2018-07-15 14:56:43      阅读:197      评论:0      收藏:0      [点我收藏+]

标签:个数   main   else   freopen   位置   转变   大小   sid   sum   

Description

有一个长度为 \(n\) 的项链,首尾相接形成环,现在你要给每一个位置一个颜色 \([1,m]\), 求所有不同的项链个数(可以通过旋转变成一样的称为相同)

Solution

根据 \(burnside\) 引理,答案为 \(\frac{1}{n}\sum_{i=1}^{|G|}c1_i\)
也就是枚举所有的置换,求不动点个数之和
置换一共有 \(n\) 种,分别为 \(1,2...n\)
枚举旋转的长度 \(i\) ,那么循环节的大小为 \(\frac{n}{gcd(i,n)}\) , 循环节个数为 \(gcd(i,n)\)
为什么?要使得 \(k*i \mod n=0\) ,那么 \(k*i\) 的最小值就是 \(lcm=\frac{i*n}{gcd(i,n)}\) ,所以 \(k\) 就等于 \(\frac{n}{gcd(i,n)}\)
那么要使得这个点为不动点(旋转 \(i\) 之后一模一样),那么同一循环节里面的点必须颜色相同
那么点的可以缩减为 \(gcd(i,n)\)
相当于问题转化为用 \(m\) 中颜色去覆盖 \(x=gcd(i,n)\) 个位置,我们设方案数为 \(calc(x,m)\)

转化为求 \(\sum_{i=1}^{n}calc(gcd(i,n),m)\)
枚举 \(gcd\)
答案就是 \(\sum_{i=1}^{n}calc(i)*phi[\lfloor\frac{n}{i}\rfloor]\)
\(calc\) 函数可以用矩乘求出

#include<bits/stdc++.h>
using namespace std;
template<class T>void gi(T &x){
    int f;char c;
    for(f=1,c=getchar();c<'0'||c>'9';c=getchar())if(c=='-')f=-1;
    for(x=0;c<='9'&&c>='0';c=getchar())x=x*10+(c&15);x*=f;
}
const int N=1e7+10,mod=9973;
int prime[N],num=0,phi[N],n,K,m;bool vis[N];
inline void priwork(){
    phi[1]=1;
    for(int i=2,t;i<N;i++){
        if(!vis[i])prime[++num]=i,phi[i]=i-1;
        for(int j=1;j<=num && i*prime[j]<N;j++){
            vis[t=i*prime[j]]=1;
            if(i%prime[j])phi[t]=phi[i]*(prime[j]-1);
            else {phi[t]=phi[i]*prime[j];break;}
        }
    }
}
inline int getphi(int x){
    if(x<N)return phi[x];
    int lim=sqrt(x),ret=x;
    for(int i=1;i<=num && prime[i]<=lim;i++){
        if(x%prime[i]==0){
            ret=ret/prime[i]*(prime[i]-1);
            while(x%prime[i]==0)x/=prime[i];
        }
    }
    if(x>1)ret=ret/x*(x-1);
    return ret;
}
struct mat{
    int a[10][10];
    inline void init(){memset(a,0,sizeof(a));}
    inline mat operator *(const mat &p)const{
        mat ret;
        for(int i=0;i<m;i++)
            for(int j=0;j<m;j++){
                ret.a[i][j]=0;
                for(int k=0;k<m;k++)
                    if(a[i][k] && p.a[k][j])
                        ret.a[i][j]=(ret.a[i][j]+a[i][k]*p.a[k][j])%mod;
            }
        return ret;
    }
}S,T;
inline int calc(int x){
    S.init();
    for(int i=0;i<m;i++)S.a[i][i]=1;
    mat D=T;x--;
    while(x){
        if(x&1)S=S*D;
        D=D*D;x>>=1;
    }
    int ret=0;
    for(int i=0;i<m;i++)
        for(int j=0;j<m;j++)ret=(ret+S.a[i][j]*T.a[i][j])%mod;
    return ret;
}
inline int qm(int x,int k){
    int sum=1;if(x>=mod)x%=mod;
    while(k){
        if(k&1)sum=sum*x%mod;
        x=x*x%mod;k>>=1;
    }
    return sum;
}
inline void work(){
    int x,y;
    cin>>n>>m>>K;
    T.init();
    for(int i=0;i<m;i++)for(int j=0;j<m;j++)T.a[i][j]=1;
    for(int i=1;i<=K;i++){
        gi(x);gi(y);x--;y--;
        T.a[x][y]=T.a[y][x]=0;
    }
    int lim=sqrt(n),ans=0;
    for(int i=1;i<=lim;i++){
        if(n%i)continue;
       ans=(ans+1ll*calc(i)*getphi(n/i))%mod;
        if(i*i!=n)ans=(ans+1ll*calc(n/i)*getphi(i))%mod;
    }
    ans=ans*qm(n,mod-2)%mod;
    cout<<ans<<endl;
}
int main(){
  freopen("pp.in","r",stdin);
  freopen("pp.out","w",stdout);
  priwork();
  int T;cin>>T;
  while(T--)work();
  return 0;
}

项链(burnside)

标签:个数   main   else   freopen   位置   转变   大小   sid   sum   

原文地址:https://www.cnblogs.com/Yuzao/p/9085718.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!