題目:
給n個青蛙,每個青蛙每次跳ai步
給一串循環的石頭,編号從0~n-1
求所有被青蛙踩過的石頭的編号和
分析:
很容易得到,第i個青蛙跳的石頭編号都是gcd(ai,m)的倍數
但m與n很大,接下來不能簡單模拟
另g=gcd(ai,m)
第i個青蛙貢獻的編号和為sum=∑k*g ,k=1,2,3,…[m/g]
=(1+2+3+…..+[m/g])*g
=[m/g]*([m/g]+1)/2 *g
(利用1+2+3+…+n的求和公式)
于是我們用公式可以O(1)求出每個青蛙的貢獻,但是這其中有重複計算,比如2和3同時是6的因數,6被計算了兩次
這時候就需要容斥原理了,這裡的容斥很巧妙
用vis[i]代表m的第i個因數是否應該做貢獻
用num[i]代表m的第i個因數做了幾次貢獻(被重複計算幾次)
那麼m的第i個因數真正所應該做的貢獻為:
[m/g] * ([m/g]+1)/2 * g * (vis[i]-num[i])
想不明白就畫個Vn圖了解一下
代碼:
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
const int Tmax=;
long long n,m,A[Tmax],in[Tmax],top,g,ans;
int vis[Tmax],num[Tmax];
long long int gcd(long long a,long long b)
{
return b==?a:gcd(b,a%b);
}
void init()
{
int i;
top=;
for(i=;i<=sqrt(m);i++)
if(m%i==)
{
top++;
in[top]=i;
if(i*i!=m)
{
top++;
in[top]=m/i;
}
}
sort(in+,in+top+);
return;
}
void work()
{
int i,j;
for(i=;i<=n;i++)
{
if(A[i]>m) g=gcd(A[i],m);
else g=gcd(m,A[i]);
for(j=;j<=top;j++)
if(in[j]%g==) vis[j]=;
}
for(i=;i<=top;i++)
{
ans+=(m/in[i])*(m/in[i]-)/*in[i]*(vis[i]-num[i]);
for(j=i+;j<=top;j++)
if(in[j]%in[i]==) num[j]+=vis[i]-num[i];
}
return;
}
int main()
{
int T,kase,i;
scanf("%d",&T);
for(kase=;kase<=T;kase++)
{
scanf("%lld%lld",&n,&m);
for(i=;i<=n;i++)
scanf("%lld",&A[i]);
init();
ans=;
memset(vis,,sizeof(vis));
memset(num,,sizeof(num));
work();
printf("Case #%d: %lld\n",kase,ans);
}
return ;
}