poj 2154 polya定理 + 欧拉函数

题意:n种颜色的珠子可组成多少种长度为n的项链?(旋转算一种,不考虑翻转)n<1000000000,结果对p取余,p<30000

分析:本题数据规模很大,若枚举旋转的长度会超时。

假设旋转 i 步,则循环节个数为gcd( i, n ) (证明在这里) ,循环节长度 L = n / gcd(i, n). 则 L | n,可以枚举L,并计算gcd(i, n) = n / L的 i 的个数。

设gcd(i, n) = t,  i = kt,n = Lt, gcd(kt, Lt) = t 等价于 gcd(k, L)=1,这样的 k 有 φ( L )个, 即 i 有 φ( L )个。

于是答案为  (1/n)  ( φ(L)* n^(n/L) ) mod p.其中L|n。由于(1/n)不方便对p取余,可以和n^(n/L)抵消,所以答案为∑ ( φ(L)* n^(n/L - 1) ) mod p.

优化:

1、L|n,则 n/L | n,所以L只需要枚举到 sqrt( n )。

2、预处理n,得到 n 的所有素因子pr [i] 及其个数cnt[i]。枚举L的时候,直接枚举每个素因子的个数,乘积即为L。

 

 

int pri[]={2,3,5,7,11,13}//这里要打表到31627,由于太多了,这里就不贴了

const int m = 35;
int pr[m], cnt[m], len;
int n, sqr, p, ans;

void init(int n){
    len = ans = 0;
    sqr = sqrt(n*1.0);
    memset(cnt, 0, sizeof cnt);

    for(int i = 0; pri[i] * pri[i] <= n; i++)
        if(n % pri[i] == 0){        //记录n的素因子及其个数
            pr[len] = pri[i];
            while(n % pri[i] == 0) {
                cnt[len]++;
                n /= pri[i];
            }
            len++;
        }
    if(n>1) {pr[len] = n; cnt[len++] = 1;}
}

int modexp(int a, int b, int n)     {//快速幂
    LL ans = 1, t = a;
    while(b) {
       if(b & 1) ans = ans * t % n;
       t = t * t % n;
       b >>= 1;
    }
    return (int)ans;
}

int euler1(int n){//求φ(n)
    int res = n;
    for(int i = 0; pri[i] * pri[i] <= n; i++)
        if(n % pri[i] == 0){
            res = res  /pri[i] * ( pri[i] - 1 );
            while(n % pri[i] == 0) n /= pri[i];
        }
    if(n>1) res=res/n*(n-1);//n是质因数
    return res % p;
}

void solve(int i, int L, int count){//枚举到第i个因子,已有乘积L,第i-1个因子的个数count 
                        //count若为0,表示第i-1个因子没选,即L已计算过,这里不要重复计算
    if(count){//L没被计算过
        int t = n / L;
        int pow = t - 1;
        int t1 = modexp( n, pow, p );
        int t2 = euler1( L ); 
        ans = ( ans + t1 * t2 ) % p;

        if(L * L != n){//转变L 和 n/L 
            pow = L - 1;
            t1 = modexp( n, pow, p );
            t2 = euler1( t );
            ans = ( ans + t1 * t2 ) % p;
        }
    }
    
    if(i==len) return ;
    
    solve(i+1, L, 0);//第i个因子没选
    int r = 1;
    FOE(k, 1, cnt[i]){
        r *= pr[i];
        int temp = L*r;
        if(temp <= sqr) solve(i+1, L*r, k);//第i个因子选了k个
    }
}

int main(){
    #ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    #endif

    int T;
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&n,&p);
        init(n);
        solve(0, 1, 1);
        printf("%d\n", ans);
    }

    return 0;
}

 

转载于:https://www.cnblogs.com/ts65213/archive/2013/05/03/3056641.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值