zoukankan      html  css  js  c++  java
  • 题解 P4980 【【模板】Polya定理】

    【模板】Polya定理 题解

    提供一个新算法。。。

    首先,我们来分析一下题目:

    给一个有n个点的环图n种颜色,问本质不同的方案数

    那么,很明显的,这是一个polya定理(废话,题目名说明了一切)

    我们先来看看这道题的“操作”,很明显的操作就是只有一个——平移(有人说旋转,但我个人更喜欢看成平移。。。)

    我们就可以开始枚举置换了

    首先,我们枚举每次平移的单位长度,那么很明显的本质不同的平移有n种,分别是平移0-(n-1)个单位

    可以发现,每一种平移方案就是一种置换

    所以,我们可以枚举平移长度,假设现在单步平移i个单位,那么要使得序列"平移"回来,我们假设最少要k步。那么k一定满足:i*k%n==0,可以解得k=lcm(n,i)/i=n/gcd(n,i),所以就有n/k=gcd(n,i)个"循环",故而,根据polya定理,当前置换的"不动点"数为n(颜色数)^gcd(n,i),为了简写,我们把这一坨设为s[i]。

    最后,由burnside引理,我们可以知道,方案数为(frac{sum_{i=0}^{n-1}s[i]}{n})%(mod)

    于是我们可以打出代码:

    #include<bits/stdc++.h>
    using namespace std;
    const int mod=1e9+7;
    inline int ksm(int x,int y,int z){
    	int ans=1;
    	while(y){
    		if(y&1){
    			ans=(1LL*ans*x)%z;
    		}
    		x=(1LL*x*x)%z;
    		y>>=1;
    	}
    	return ans%z;
    }
    int main(){
    	int T;
    	scanf("%d",&T);
    	while(T--){
    		int n;
    		scanf("%d",&n);
    		int ans=0;
    		for(int i=0;i<n;++i){
    			ans+=ksm(n,__gcd(n,i),mod);
    			ans%=mod; 
    		}
    		ans=(1LL*ans*ksm(n,mod-2,mod))%mod;//逆元 
    		printf("%d
    ",ans);
    	}
    	return 0;
    }
    

    然后,它就T了。。。qwq

    那么,我们该怎么优化这个程序呢?

    ————————————————以下是正文——————————————————

    我们不难注意到,对于使得gcd(n,i)相同的i,对答案的贡献是一样的(先不管除以n),为n^gcd(n,i),那么,我们不妨就枚举gcd(n,i)!

    因为枚举gcd(n,i)等价与枚举n的约数,所以,我们枚举部分的复杂度就降为了(O(sqrt{n})),所以,现在我们需要做的就是尽量快速的计算这个问题:

    给出n的一个约数i,求0-(n-1)中有多少个数与n的gcd为i

    我们先把i=1和i=n的情况算出来(为了方便)。

    i=1时,满足条件的一共有φ(n)个,i=n时一个有一个(gcd(0,n)=n)

    所以,这两个的答案和为Ans1=(φ(n)*n+n^n)%mod

    那么,我们再来看看剩下的该怎么算

    我们发现,求0-(n-1)中有多少个数与n的gcd为i,因为i不等于1和n,所以问题等价于

    求1-n中有多少个数与n的gcd为i。

    而我们可以发现,满足条件的数一定是i的倍数。所以,问题又等价于:枚举1-n/i中,有多少个数j满足gcd(n,j*i)=i

    等价于求1-n/i中有多少个数j满足gcd(n/i,j)=1,很明显,这个问题的答案就是φ(n/i)

    (为什么转化了这么多步啊!抓狂ing。。。)

    那么,我们就需要较快的求φ(x)了

    本来,我们可以通过预处理,用O(n)的时间求出所有的φ,但,问题是n<=1e9,时间上如果优秀的话,估计可能卡过,但空间上就明显不允许了,所以,我们必须找到一个较好的方法。

    我们知道,φ是一个积性函数,所以,我们可以根据它的性质,找到它的一个质因数,然后递归求解。。。

    参照杜教筛,我们可以先将较小的φ预处理出来(我是预处理到1e7)

    然后,我们先用Miller_rabin算法判断x是否是质数,如果是,我们就返回x-1,否则,我们就可以用pollard_rho算法来分解x,递归求解,中间可以套个map来加速。问题得解。

    当然,记得特判下n=1的答案(因为一开始我们同时统计了i=1和i=n的答案,于是对于1,就多算了一次)

    半伪代码:

    #include<bits/stdc++.h>
    using namespace std;
    const int mod=1e9+7,N=1e7+1;
    map<int,int>f;
    int T,fi[N],zhi[N>>1],e;
    inline int ksm(int x,int y,int z){
    	int ans=1;
    	while(y){
    		if(y&1){
    			ans=(1LL*ans*x)%z;
    		}
    		x=(1LL*x*x)%z;
    		y>>=1;
    	}
    	return ans;
    }
    inline void sai(int maxe){
    	T=maxe;fi[1]=1;
    	for(int i=2;i<=maxe;++i){
    		if(!fi[i]){
    			fi[i]=i-1;zhi[++e]=i;
    		}
    		for(int j=1;j<=e;++j){
    			if(i*zhi[j]>maxe){
    				break;
    			}
    			if(i%zhi[j]==0){
    				fi[i*zhi[j]]=fi[i]*zhi[j];
    				break;
    			}
    			fi[i*zhi[j]]=fi[i]*fi[zhi[j]];
    		}
    	}
    }
    inline bool Miller_rabin(int x){
    	//偷懒省略 
    }
    inline int pollard_rho(int x){
    	//偷懒省略 
    }
    inline int calc(int x){
    	if(x<=T){
    		return fi[x];
    	}
    	if(f.find(x)!=f.end()){
    		return f[x];
    	}
    	int ret=pollard_rho(x);
    	if(ret==x){
    		return x-1;
    	}
    	return f[x]=((x/ret)%ret==0?ret*calc(x/ret):(ret-1)*calc(x/ret));
    }
    int main(){
    	srand(1);
    	sai(1e7);//预处理 
    	int T;
    	scanf("%d",&T);
    	while(T--){
    		int n;
    		scanf("%d",&n);	
            if(n==1){
                puts("1");
                continue;
            }
    		int ans=(ksm(n,n,mod)+(1LL*n*calc(n))%mod)%mod;
    		for(int i=sqrt(n);i>=2;--i){//一次平移的距离
    			if(n%i==0){
    				ans+=(1LL*ksm(n,i,mod)*calc(n/i))%mod;
    				ans%=mod;
    				if(n/i!=i){
    					ans+=(1LL*ksm(n,n/i,mod)*calc(i))%mod;
    					ans%=mod;
    				}
    			}
    		}
    		ans=(1LL*ans*ksm(n,mod-2,mod))%mod;//逆元 
    		printf("%d
    ",ans);
    	}
    	return 0;
    }
    
  • 相关阅读:
    FPGA quartus开发中常见的错误处理
    verilog中wire与reg类型的区别
    VC++6.0中ClassView中类消失 解决方案[转自网络]
    C++ 和 MFC的学习
    最近单片机编程中的心得
    #ifdef __cplusplus extern "C" { #endif”的定义的含义
    ES6字符串拼接新方法-模板字符串表达式
    JavaScript事件参数对象event
    JavaScript offset家族
    详解JavaScript中的replace()函数
  • 原文地址:https://www.cnblogs.com/ThinkofBlank/p/11527113.html
Copyright © 2011-2022 走看看