HDU 5514 Frogs (容斥原理)

题目链接 : http://acm.hdu.edu.cn/showproblem.php?pid=5514

 

题意 : 

有m个石子围成一圈, 有n只青蛙从跳石子, 都从0号石子开始, 每只能越过a[i]个石子

问所有被至少踩过一次的石子的序号之和

 

思路 : 

不难发现, 从0开始, 每次越过a[i]个石子, 那么gcd(a[i], m)的倍数都能被经过

石子 k * (gcd(a[i], m)) < m 的都被算入

但如果按单独每个a[i]来计算对答案的贡献, 肯定会有重复, 重复部分就是lcm(a[i], a[j])的倍数

这里要用容斥的思想解决

 

第一次正式接触容斥, 但其实已经有很多地方用过这种思想了

比如二维树状数组求面积, 求一个范围内能整除若干个数的个数, 还有某些概率计算

这道题中, 考虑每个gcd(a[i], m)的贡献时

首先 x = gcd(a[i], m) 一定是m的因子, x的贡献是 (m / x) * (m / x - 1) * / 2 * x

因为Sum(k * x) (1 <= k < m / x), 提取x为公因子, k就是一个等差数列求和

此时若有 y = gcd(a[j], m), y % x == 0, 则要减去一次y产生的贡献

 

所以解法是, 先用处理出所有m的因子, 储存在一个数组d中

对每个a[i], 都算出x = gcd(a[i], m), 遍历m的因子,

若d[i] % m == 0, 就将这个因子的贡献标记为1, vis[i] = 1, 表示这个因子应该做贡献

用一个数组记录每个因子做过的贡献, 一开始全为0

再遍历m的因子, 如果当前这个因子应该做的贡献和已经做的贡献不等, 补上贡献的次数是vis[i] - num[i]

所以套用计算贡献的公式, 当前因子x做的贡献为(m / x) * (m / x - 1) * / 2 * x * (vis[i] - num[i])

这时就要用容斥了, 这些因子中, 如果有y能整除x, x进行贡献的同时, 因子y也同时被贡献了vis[i] - num[i]次

那么对应因子y的num[j]就要加上vis[i] - num[i]

如果出现了vis[i] - num[i] < 0的情况, 说明这个因子被多贡献了, 减去相应次数的贡献即可

 

重现没有做出来, 代码是参考了网上大牛的

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long LL;

const int MAXN = 1e4+10;

int a[MAXN];
int d[MAXN];
int vis[MAXN];
int num[MAXN];

int GCD(int a, int b)
{
    int r = a % b;
    while(r) {
        a = b;
        b = r;
        r = a % b;
    }
    return b;
}

bool cmp(int a, int b)
{
    return a < b;
}

void Init()
{
    memset(vis, 0, sizeof(vis));
    memset(num, 0, sizeof(num));
}

int main()
{
    int t;
    int n, m;

    scanf("%d", &t);
    for(int cas = 1; cas <= t; cas++) {
        Init();
        scanf("%d %d", &n, &m);
        int cnt = 0;
        for(int i = 1; i * i <= m; i++) {
            if(m % i == 0) {
                d[cnt++] = i;
                if(i * i != m) {
                    d[cnt++] = m / i;
                }
            }
        }
        sort(d, d+cnt, cmp);
        for(int i = 0; i < n; i++) {
            scanf("%d", &a[i]);
            a[i] = GCD(a[i], m);
            for(int j = 0; j < cnt; j++) {
                if(d[j] % a[i] == 0) {
                    vis[j] = 1;
                }
            }
        }
        LL ans = 0;
        vis[cnt-1] = 0;
        for(int i = 0; i < cnt; i++) {
            if(vis[i] != num[i]) {
                LL temp = m / d[i];
                ans += temp * (temp - 1) / 2 * d[i] * (vis[i] - num[i]);
                for(int j = i + 1; j < cnt; j++) {
                    if(d[j] % d[i] == 0) {
                        num[j] += vis[i] - num[i];
                    }
                }
            }
        }
        printf("Case #%d: %I64d\n", cas, ans);
    }


    return 0;
}
View Code

 

posted @ 2015-11-03 10:50  Quinte  阅读(1408)  评论(0编辑  收藏  举报