HDU - 5514 Frogs (容斥)

3 篇文章 0 订阅


题意:

有 n 个青蛙,第 i 个青蛙每次只能够跳 ai 步,现在有 m 个石头围成一圈,编号为 0 到 m−1,现在青蛙可以围着这个石头组成的圆跳无限次,每跳一次就会占领这个石头,可以无限占领,现在问你的是这 n 个青蛙占领的石头的编号的总和是多少。

思路: 
先说第一种方法: 
我们可以发现对于每个ai,他所能经过的石头为 k*gcd(m,ai).但是我们发现比如第一个样例 
2 12 
9 10 ,gcd分别为2 ,3。 
2 经过 0 2 4 6 8 10 
3经过 0 3 6 9 
这就会有重复的,比如6就会被算两次.这就需要去掉了,这时候就可以想到这个题需要容斥一下了.那么关键是怎么容斥?


现在我们已知  gi=gcd(m,ai) , 我们还是考虑  m  的因子 x ,如果这个因子  x  符合条件,那么我们来计算  x  对答案做出的贡献:  (mx1)m2 。 
首先我们预处理出  m  的除了  1  m  的因子  fac[i] ,如果这个因子 fac[i]%g[i]=0  的话(其中  g[i] 为可能出现的最大公约数),那么我们将其用一个  vis  数组进行标记为  1 ,然后再用一个数组  num  记录当前因子对答案做过的贡献,初始值为  0 ,那么有第 i  个因子做出的贡献为:  (mx1)m2(vis[i]num[i])  
因为可能有重复的,所以我们就对所有能够整除因子  fac[i]  的因子全都加上  vis[i]num[i] , 然后就可以计算答案了。


总结:很好的容斥把, 对于一个gcd, 如果把他加入贡献,他所有的倍数也都加入了贡献, 所以我们把所有a[i]跟m的gcd,在m因子里的倍数都标记一下vis, 然后枚举m的因子, 用num记录每个数贡献了几次, 当前枚举了一个数, 他所有的倍数都贡献了一次,下次他倍数再出现就不用算贡献了,每个数只能贡献一次, 所以其他枚举到的数,如果他的倍数有之前贡献过一次的数了,他就要减去了。很好的容斥。还是要观察题目, gcd(m, a),这不就是说所有gcd都是m的因子,他的lcm也是m的因子吗。。


#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn = 1e4 + 5;
int cnt[maxn], n, m;
vector<int> fac;
void init()
{
    fac.clear();
    memset(cnt, 0, sizeof(cnt));
    for(int i = 1; i*i <= m; i++)
    {
        if(m % i == 0)
        {
            fac.push_back(i);
            if(i != m/i) fac.push_back(m/i);
        }
    }
}
int main()
{
    int _, ca = 1;
    cin >> _;
    while(_--)
    {
        scanf("%d%d", &n, &m);
        init();
        sort(fac.begin(), fac.end());
        int x, flag = 0;
        for(int i = 0; i < n; i++)
        {
            scanf("%d", &x);
            int gcd = __gcd(m, x);
            if(gcd == 1) flag = 1;
            for(int j = 0; j < fac.size(); j++) //记录所有有贡献 的因子
                if(fac[j]%gcd == 0)
                    cnt[j] = 1;
        }
        if(flag)
        {
            printf("Case #%d: %lld\n", ca++, (ll)m*(m-1)/2);
            continue;
        }
        ll ans = 0;
        for(int i = 0; i < fac.size(); i++)
        {
            if(!cnt[i])  continue;
            ans += (ll)(m/fac[i]-1)*m/2*cnt[i];
            for(int j = i + 1; j < fac.size(); j++)
                if(fac[j]%fac[i] == 0)
                    cnt[j] -= cnt[i];
        }
        printf("Case #%d: %lld\n", ca++, ans);
    }
    return 0;
}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值