POJ Color有感

 

Color
Time Limit: 2000MS Memory Limit: 65536K
Total Submissions: 4655 Accepted: 1563

Description

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

Source

POJ Monthly,Lou Tiancheng

 

此题是大规模的polya置换,小范围的进行联通分量的搜索已经无济于事。现在我们要最求更快速的方法,那么如何求得呢?这里有很多组合数学书上没有的结论,

第一就是:N个元素x位的平移置换的连通分量为:GCD(N,x),神奇吧!

第二就是重点了:就是快速求得有多首先,直接应用polya定理就可以得到ans=(N^gcd(0,N)+N^gcd(1,N)+…+N^gcd(N-1,N))/N,如果直接计算的话,即便应用快速幂,也改变不了循环计算N项和的复杂度。于是便不得不从gcd的值入手看是否能合并一些项达到化简的目的了。尽管一共有N项,但gcd的值一定到不了N项,因为gcd是N的约数,而一个数的约数是没有那么多的。顺着这个思路想的话,我们有没有办法更快地求得N的所有约数呢?这一点是可以在O(sqrt(N))的时间内办到的。我们只要枚举sqrt(N)以内的能够整除N的整数,就可以找到N所有的约数,如果同时我们可以O(1)的时间求出指数为该约数的项一共有多少的话,那么这个题整体就是O(sqrt(N))的复杂度了。那么这时就还剩一个问题没有解决了,对于N的任意一个约数,我们不妨设其值为d,指数为d的项一共有多少呢?也就是说一共有多少个满足gcd(x,N)=d的x呢?实际上,由于N和x的最大公约数是d,那么x/d和N/d必然互素,同时x<N,那么x的数量也就是N/d的欧拉函数值。最后还有一个事情,就是/N的问题,由于和式的每一项都是N^gcd,于是每项直接变成N^(gcd-1)即可。解决完上述几个问题之后,程序就不难写了,不过由于这个题时限卡得比较紧,所以求欧拉函数时要用素数表优化,同时筛素数的时候又可以把一些比较小的数的欧拉函数顺便也求出来。

其实也可不必找出所有的素数,先将N分解因子,然后通过枚举所有的约数(这个DFS一下即可),在DFS的过程中,也要注意现场还原,这么基本的东西也把我搞的debug了好长时间才发现,自己原来没有现场还原才导致WA好多次。

 

 

#include <iostream>
#include <cstdio>
using namespace std;
#define LL  long long
const int MAX = 201;
int sizea[MAX];
LL a[MAX];
LL N, M, P, x;
LL res;
int size;

LL quick(LL base, LL p) {
    if (p == 0) {
        return 1 % P;
    }
    if (p & 1) {
        return (base % P)* (quick(base, p - 1) % P) % P;
    } else {
        LL temp = quick(base, p / 2) % P;
        return temp * temp % P;
    }
}

LL cal_rula(LL num) {
    for (int i = 0; i < size; i++) {
        if (sizea[i] > 0) {
            num /= a[i];
            num *= (a[i] - 1);
        }
    }
    return num;
}

/*枚举,居然忘了还原,写惨了!!!*/
void DFS(int depth, LL num) {
    if (depth >= size) {
        //        cout << num << endl;
        res = res + (cal_rula(N / num) % P) * quick(N, num - 1) % P;
        res %= P;
        return;
    }
    DFS(depth + 1, num);
    /*比较之后*/
    int temp = sizea[depth];
    while (sizea[depth]) {//这种写法要注意,不能在里面减,虽然先判断,但还是再减的。
        sizea[depth]--;
        num *= a[depth];
        DFS(depth + 1, num);
    }
    sizea[depth] = temp; //让人哭的错误,居然最基本的还原都忘了!!!
}

int main() {
    int T;
    while (scanf("%d", &T) != EOF) {
        while (T--) {
            scanf(" %llu", &N);
            scanf(" %llu", &P);
            x = N;
            size = 0;
            LL temp = N;
            for (LL i = 2; i * i <= temp; i++) {
                if (temp % i == 0) {
                    sizea[size] = 0;
                    a[size] = i;
                    while (temp % i == 0) {
                        temp /= i;
                        sizea[size]++;
                    }
                    size++;
                }
            }
            if (temp > 1) {
                sizea[size] = 1;
                a[size] = temp;
                size++;
            }
            res = 0;
            DFS(0, 1);
            printf("%llu\n", res % P);
        }
    }
    return 0;
}

   

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值