题意:有m个石子围成一圈, 有n只青蛙从跳石子, 都从0号石子开始, 每只能越过xi个石子。问所有被至少踩过一次的石子的序号之和。
思路:
首先对于每个青蛙而言,它能到达的位置是它每次能跳的长度x与m的GCD的倍数
但是对于很多的GCD的时候。可能会多算,,比如2的倍数,3的倍数,此时6的倍数就被多算了。。
其实也就是上边的那个思想用容斥的办法,既然6多算了一次,剪掉一次6的倍数就好。。
GCD(x,m)=p,p肯定是m的约数。我们首先考虑所有m的约数,,如果是青蛙能跳的倍数的就标记为1,表示这个约数要做出贡献值。。如果约束q做出了贡献,那么q的倍数的贡献也就要标记上。。
比如计算2的时候6被标记了一次,计算3的时候,6被也标记了一次,计算到6的时候,本应爱是visit==1但是我有num==2,标记计算了两次,所以要剪掉。visit-num,num也有可能是多次反复跟新约数的贡献值的次数。。
PS:这个容斥做得好巧妙!
Code:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e4 + 10;
ll n,m;
int fac[maxn];
int vis[maxn];
int num[maxn];
int main()
{
int T;
scanf("%d",&T);
for(int ii = 1; ii <= T; ii ++)
{
memset(vis,0,sizeof(vis));
memset(num,0,sizeof(num));
scanf("%I64d%I64d",&n,&m);
int tot = 0;
for(int i = 1; i * i <= m;i ++)
{
if(m % i == 0)
{
fac[tot ++] = i;
if(i * i != m)
fac[tot ++] = m/i;
}
}
sort(fac,fac + tot);
tot --;
for(int i = 1; i <= n;i ++)
{
ll x;
scanf("%I64d",&x);
ll d = __gcd(x,m);
for(int j = 0; j < tot; j ++)
if(fac[j] % d == 0)
vis[j] = 1;
}
ll ans = 0;
for(int i = 0; i < tot; i ++)
{
if(vis[i] != num[i])
{
ll t = (m - 1)/fac[i];
ans += t * (t + 1) / 2 * fac[i] * (vis[i] - num[i]);
for(int j = i + 1; j < tot; j ++)
{
if(fac[j] % fac[i] == 0)
num[j] += vis[i] - num[i];
}
}
}
printf("Case #%d: %I64d\n",ii,ans);
}
return 0;
}