HDU - 5514 Frogs —— 容斥

题意:

有n只青蛙m块石头,每只青蛙跳的长度是固定的,问所有被跳过的石头的下标和
思路:

转化为求0~m-1之间,gcd(ai,m)的倍数的和

求一个数的倍数和是可以直接用等差数列计算出的,但是会有数被重复计算,我们要做的就是用容斥计算重复的数

首先,gcd(ai,m)一定是m的因子,所以需要容斥的数只有可能是m的因子,1e9的数的因子最多有1600+,所以可以通过预处理出m的因子来容斥

用cnt来记录容斥时每个数的计算次数,遇到多算的数就减掉,少算的数就加上,然后遍历其后的因子做相应的操作

这样有一个问题,重复算的数不一定是m的因子,比如m=36,gcd分别为3和4,24是会被重复计算的但是并不在36的因子里,这时如何保证24被计算了正确的次数?

这个问题我也想了好久,最后发现被重复计算的数和它的几个gcd的lcm的计算次数是一样的,比如说刚刚的例子,lcm(3,4)=12,24的计算次数和12的计算次数是一样,而这个lcm也就是12一定是m的因子,所以只要保证了12计算次数是正确的那么24的计算次数也是正确的

#include <iostream>
#include <cstdio>
#include <cmath>
#include <vector>
#include <map>
#include <set>
#include <stack>
#include <queue>
#include <string>
#include <cstring>
#include <algorithm>
using namespace std;
#define ll long long
#define max_ 200100
#define mod 1000000007
#define inf 0x3f3f3f3f
#define eps 1e-9
int n,m;
int casnum=1;
int cnt[2000],fac[2000];
int tot;
int main(int argc, char const *argv[]) {
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        tot=0;
        for(int i=1;i*i<=m;i++)
        {
            if(m%i==0)
            {
                fac[++tot]=i;
                if(i!=m/i)
                {
                    fac[++tot]=m/i;
                }
            }
        }
        sort(fac+1,fac+1+tot);
        memset(cnt,0,sizeof cnt);
        for(int i=1;i<=n;i++)
        {
            int x;
            scanf("%d",&x);
            x=__gcd(x,m);
            for(int j=1;j<=tot;j++)
            {
                if(fac[j]%x==0)
                cnt[j]=1;
            }
        }
        ll ans=0;
        for(int i=1;i<=tot;i++)
        {
            if(cnt[i]==0)
            continue;
            ans+=(ll)fac[i]*(m/fac[i]+1)*(m/fac[i])/2*cnt[i];
            for(int j=i+1;j<=tot;j++)
            {
                if(fac[j]%fac[i]==0)
                {
                    cnt[j]-=cnt[i];
                }
            }
        }
        printf("Case #%d: %lld\n",casnum++,ans-m);
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值