容斥或欧拉(hdu 5514)

传送门
题意:给定n个青蛙,每一个青蛙的步长是ai,可以无限次跳,总共有m块石头,编号从0~m-1,围成一个圆,刚开始所有的青蛙都在0号石头上,青蛙跳到的石头都被占据,问被占据的所有的石头的编号和时多少。
思路:假如a是一只青蛙的步长,则a*k是它可能走的所有情况(k为正整数),a*k%m的所有值就是这只青蛙你能跳到的所有石头,但是这样根本就求不出来,(a*k+b*m)%m的值和上面的值是相同的,假如(a*k+b*m)%m=c方程有解的条件就是gcd(a,m)|c,所以c的所有解就是k*gcd(a,m),就是说没一直青蛙占领的石头都是自己的步长a和m的最大公约数的整数倍,但是直接求是没发求的,因为m太大了,下面给出两种解法。

大佬博客

欧拉函数的解法

思路:设x=gcd(ai,m),g=gcd(i,m),(i代表的是0~m)。g肯定是m的所有因子,而x也是m的因子,所以g的集合里面包含x集合,而且g集合里面得到每一个不同的g的值,对应的刚好是1~m不会有重复的出现,当x|g时g集合中的所有值都能由x=gcd(ai,m)这只青蛙跳到,所以我们只要计算出有多少的g必定被跳到,将这么多g集合中的数字加起来就是答案了。这里有一个定理在 [1,x] 中与 x 互素的数的和为: phi(x)x2 ,所以一个g集合里的所有的和为 phi(mg)mg2g ,同一个g集合中的元素提出一个g,剩下的所有数肯定是小于
mg 且与其互素的所有数根据上面的定理得到每一个g集合的贡献。

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#define LL long long
using namespace std;
const int maxn=1e6+10;
int vis[maxn],num[maxn],fac[maxn];
int get_eular(int n)//得到n的欧拉函数值
{
    int ans=n;
    for(int i=2; i*i<=n; i++)
    {
        if(n%i==0)
        {
            ans-=ans/i;
            while(n%i==0)
                n/=i;
        }
    }
    if(n>1) ans-=ans/n;
    return ans;
}
int main()
{
    int ncase;
    scanf("%d",&ncase);
    for(int ca=1; ca<=ncase; ca++)
    {
        memset(vis,0,sizeof(vis));
        int n,m,cont=0;
        scanf("%d%d",&n,&m);
        int tmp=sqrt(m);
        for(int i=1; i<=tmp; i++)//分解m的因子
        {
            if(m%i==0)
            {
                fac[cont++]=i;
                if(i*i!=m)
                    fac[cont++]=m/i;
            }
        }
        for(int i=0; i<n; i++)
        {
            int a;
            scanf("%d",&a);
            tmp=__gcd(a,m);
            for(int j=0; j<cont; j++)//找到所有tmp对应的g集合
            {
                if(fac[j]%tmp==0)//整除说明fac[j]所代表的g集合肯定能被跳到
                    vis[j]=1;
            }
        }
        LL ans=0;
        for(int i=0; i<cont; i++)
        {
            if(vis[i]&&fac[i]!=m)//注意是0~(m-1)
                ans+=1ll*get_eular(m/fac[i])*m/2;
        }
        printf("Case #%d: %lld\n",ca,ans);
    }
}

容斥解法

思路:答案是x=gcd(a,m),的所有倍数,但是如果直接算的话会有重复的,比如2和3,6会被重复计算两遍,这样就要用到容斥,但是m太大了,根本无法容斥。如果a和b都是m的因子则lcm(a,b)=x如果不大于m的话,x也是m的因子,而a的倍数和b的倍数重复的部分都是x的倍数,所以我们可以通过对m的因子进行容斥得到答案,vis[i]标记第i个因子需要被计算几次,而num[i]代表第i个因子已经被计算过几次了,因子从小到大枚举计算就好了。
x的倍数的计算是提出来个x就是等差数列最后一项是 mx1 因为不能包括m一共有 mx1 项,所以贡献值为 (mx1+1)(mx1)2x = m(mx1)2

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#define LL long long
using namespace std;
const int maxn=1e6+10;
int vis[maxn],num[maxn],fac[maxn];
int main()
{
    int ncase;
    scanf("%d",&ncase);
    for(int ca=1; ca<=ncase; ca++)
    {
        memset(vis,0,sizeof(vis));
        memset(num,0,sizeof(num));
        int n,m,cont=0;
        scanf("%d%d",&n,&m);
        int tmp=sqrt(m);
        for(int i=1; i<=tmp; i++)//计算m的因子
        {
            if(m%i==0)
            {
                fac[cont++]=i;
                if(i*i!=m)
                    fac[cont++]=m/i;
            }
        }
        sort(fac,fac+cont);//排序 从小到大枚举
        cont--;//不能计算m因为是从0~(m-1)的
        for(int i=0; i<n; i++)
        {
            int a;
            scanf("%d",&a);
            tmp=__gcd(a,m);
            for(int j=0; j<cont; j++)//统计那些因子应该被计算
            {
                if(fac[j]%tmp==0)
                    vis[j]=1;
            }
        }
        LL ans=0;
        for(int i=0; i<cont; i++)
        {
            if(vis[i]!=num[i])//到目前为止第i个因子的计算次数不符合要求
            {
                LL he=1ll*m/fac[i];
                ans+=m*(he-1)/2*(vis[i]-num[i]);//容斥计算
                tmp=vis[i]-num[i];
                for(int j=i+1; j<cont; j++)//更新fac[i]的倍数被计算的次数
                    if(fac[j]%fac[i]==0)
                    num[j]+=tmp;
            }
        }
        printf("Case #%d: %lld\n",ca,ans);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值