hdu5514-Frogs(容斥原理)好题

题意:有m个石头围成一圈,编号分别为0到m-1,现在有n只青蛙,都在0号石头上,第i只青蛙会从当前编号为p的石头跳到编号为(p+ai)%m的石头上。被青蛙经过的石头都会被占领,求这m块石头中所有被占领过的石头的编号和。

题解:对于第i只青蛙,它所能跳到的最小的位置是gcd(ai, m)

               设最小位置为z,需要跳x圈,跳了y步,可得方程:x*m+z=ai*y

               即:x*m-ai*y = z  由扩展欧几里得定理可知,z的最小整数解为gcd(m,ai)

       因为对于单独的每一只青蛙计算结果会重复计算,所以利用容斥对每一个m的因子计算。

       首先对于每一个x=gcd(ai,m),如果m的一个因数fac%x==0,那么fac就会被跳到。

       然后对于每一个会碰到的因数计算,当m的一个因数j的因数i被计算的时候,j就会被重复计算,要减去。

       虽然题解很有道理,但是我想了好久也没明白容斥不是奇加偶减吗,怎么这么算了= =

       后来搜题解明白了此题gcd太多,二进制枚举会爆longlong,dfs也会超时,http://www.acmtime.com/?p=864 一个神奇的剪枝。

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>

using namespace std;

const int N = 100005;
int fac[N], cnt;
int cc[N];

void cal(int x) {
    cnt = 0;
    int limit = sqrt(x);
    fac[cnt++] = 1;
    for (int i = 2; i < limit; ++i) {
        if (x % i == 0) fac[cnt++] = i, fac[cnt++] = x/i;
    }
    if (limit*limit == x) fac[cnt++] = limit;
    else if (x % limit == 0) fac[cnt++] = limit, fac[cnt++] = x/limit;
    sort(fac, fac+cnt);
}

int main()
{
    int T, cas = 0;
    int n, m;
    scanf("%d", &T);
    while (T--) {
        printf("Case #%d: ", ++cas);
        scanf("%d%d", &n, &m); //1e4 1e9
        cal(m);
        memset(cc, 0, sizeof cc);
        int ai;
        for (int i = 0; i < n; ++i) {
            scanf("%d", &ai);
            int gcd = __gcd(ai, m);
            for (int i = 0; i < cnt; ++i) {
                if (fac[i] % gcd == 0) cc[i] = 1;
            }
        }
        long long ans = 0;
        for (int i = 0; i < cnt; ++i) {
            if (cc[i] == 0) continue;
            long long tmp = (m-1) / fac[i];
            ans += tmp * (tmp+1) / 2 * fac[i] * cc[i];
            for (int j = i+1; j < cnt; ++j) {
                if (fac[j] % fac[i] == 0) cc[j] -= cc[i];
            }
        }
        printf("%lld\n", ans);
    }
    return 0;
}

 

posted @ 2016-10-15 14:09  我不吃饼干呀  阅读(866)  评论(2编辑  收藏  举报