传送门
题意:给定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)∗mg2∗g
,同一个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就是等差数列最后一项是
mx−1
因为不能包括m一共有
mx−1
项,所以贡献值为
(mx−1+1)∗(mx−1)2∗x
=
m∗(mx−1)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);
}
}