poj2154——polya定理+欧拉函数

题目链接:poj.org/problem?id=2154

Beads of N colors are connected together into a circular necklace of N beads (N<=1000000000). Your job is to calculate how many different kinds of the necklace can be produced. You should know that the necklace might not use up all the N colors, and the repetitions that are produced by rotation around the center of the circular necklace are all neglected.

You only need to output the answer module a given number P.

Input

The first line of the input is an integer X (X <= 3500) representing the number of test cases. The following X lines each contains two numbers N and P (1 <= N <= 1000000000, 1 <= P <= 30000), representing a test case.

Output

For each test case, output one line containing the answer.

Sample Input

5
1 30000
2 30000
3 30000
4 30000
5 30000

Sample Output

1
3
11
70
629

题目翻译:

将正n边形的n个顶点用n种颜色染色,问有多少种方案(答案mod p,且可由旋转互相得到的算一种)

题解:

根据Polya定理我们可以得知答案为

res=\frac{1}{n}\sum_{i=1}^{n-1}{n}^{gcd(i,n)}

如果数据范围小的话,就是很简单的求项链总数的脑残题(因为只考虑旋转)。

但是由于数据范围的限制,我们不能直接枚举去求,因此我们需要对原式进行化简(感觉有点像莫比乌斯反演)。

(不想自己写了,就搬了网上的解析(手动滑稽))

剩下的就好办了,我们先预处理求出一部分质数,然后我们再在O(sqrt(n))范围内去枚举就行了。

求phi(n)的模板我们也有,因此答案很容易就出来。

不过注意一下,求n的d-1次幂时要用快速幂求。

还有最后要特判一下 i*i==n 的情况

#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#define rp(i, s, t) for (i = s; i <= t; i++)
#define RP(i, s, t) for (i = t; i >= s; i--)
#define ll long long
#define ull unsigned long long
using namespace std;
inline int read()
{
    int x = 0, t = 1;
    char ch = getchar();
    while ((ch < '0' || ch > '9') && ch != '-')
        ch = getchar();
    if (ch == '-')
        t = -1, ch = getchar();
    while (ch <= '9' && ch >= '0')
        x = x * 10 + ch - 48, ch = getchar();
    return x * t;
}
inline void write(int x)
{
    char F[200];
    int tmp = x > 0 ? x : -x;
    if (x < 0)
        putchar('-');
    int cnt = 0;
    while (tmp > 0)
    {
        F[cnt++] = tmp % 10 + '0';
        tmp /= 10;
    }
    while (cnt > 0)
        putchar(F[--cnt]);
}
int n,p;
const int N=1e5+7;
int visited[N],prime[N];
int tot;
void init()//预处理求出一部分的质数
{
    memset(visited,0,sizeof visited);
    tot=0;
    int i;
    rp(i,2,N-1){
        if(!visited[i]){
            prime[tot++]=i;
            for(int j=i+i;j<N;j+=i)
                visited[j]=1;
        }
    }
}
ll phi(ll n)//求n得欧拉函数的值
{
    ll res=n;
    for(int i=0;prime[i]*prime[i]<=n;i++){
        if(n%prime[i]==0){
            res=res-res/prime[i];
            while(n%prime[i]==0) n/=prime[i];
        }
    }
    if(n>1) res=res-res/n;
    return res%p;
}
ll quick_mod(ll a,ll b)//快速幂
{
    ll res=1;
    while(b){
        if(b&1) res=(res*a)%p;
        a=(a*a)%p;
        b>>=1;
    }
    return res%p;
}
int main()
{
    init();
    int T = read();
    while (T--){
        n=read(),p=read();
        ll ans=0;
        int i;
        for(i=1;i*i<=n;i++){
            if(i*i==n)//i*i==n的时候特判 
                ans=(ans+phi(i)*1ll*quick_mod(n,i-1)%p)%p;
            else if(n%i==0)//如果i时n的约数,那么n/i也是n的约数,因此我们可以一次求两个。
                ans=(ans+phi(n/i)*1ll*quick_mod(n,i-1)%p+phi(i)*1ll*quick_mod(n,n/i-1)%p)%p;
        }
        printf("%lld\n",ans%p);
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值