题意:
有 n 个青蛙,第 i 个青蛙每次只能够跳 ai 步,现在有 m 个石头围成一圈,编号为 0 到 m−1,现在青蛙可以围着这个石头组成的圆跳无限次,每跳一次就会占领这个石头,可以无限占领,现在问你的是这 n 个青蛙占领的石头的编号的总和是多少。
思路:
先说第一种方法:
我们可以发现对于每个ai,他所能经过的石头为 k*gcd(m,ai).但是我们发现比如第一个样例
2 12
9 10 ,gcd分别为2 ,3。
2 经过 0 2 4 6 8 10
3经过 0 3 6 9
这就会有重复的,比如6就会被算两次.这就需要去掉了,这时候就可以想到这个题需要容斥一下了.那么关键是怎么容斥?
现在我们已知
gi=gcd(m,ai)
, 我们还是考虑
m
的因子
x
,如果这个因子
x
符合条件,那么我们来计算
x
对答案做出的贡献:
(mx−1)∗m2
。
首先我们预处理出
m
的除了
1 和 m
的因子
fac[i]
,如果这个因子
fac[i]%g[i]=0
的话(其中
g[i]
为可能出现的最大公约数),那么我们将其用一个
vis
数组进行标记为
1
,然后再用一个数组
num
记录当前因子对答案做过的贡献,初始值为
0
,那么有第
i
个因子做出的贡献为:
(mx−1)∗m2∗(vis[i]−num[i])
因为可能有重复的,所以我们就对所有能够整除因子
fac[i]
的因子全都加上
vis[i]−num[i]
, 然后就可以计算答案了。
总结:很好的容斥把, 对于一个gcd, 如果把他加入贡献,他所有的倍数也都加入了贡献, 所以我们把所有a[i]跟m的gcd,在m因子里的倍数都标记一下vis, 然后枚举m的因子, 用num记录每个数贡献了几次, 当前枚举了一个数, 他所有的倍数都贡献了一次,下次他倍数再出现就不用算贡献了,每个数只能贡献一次, 所以其他枚举到的数,如果他的倍数有之前贡献过一次的数了,他就要减去了。很好的容斥。还是要观察题目, gcd(m, a),这不就是说所有gcd都是m的因子,他的lcm也是m的因子吗。。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn = 1e4 + 5;
int cnt[maxn], n, m;
vector<int> fac;
void init()
{
fac.clear();
memset(cnt, 0, sizeof(cnt));
for(int i = 1; i*i <= m; i++)
{
if(m % i == 0)
{
fac.push_back(i);
if(i != m/i) fac.push_back(m/i);
}
}
}
int main()
{
int _, ca = 1;
cin >> _;
while(_--)
{
scanf("%d%d", &n, &m);
init();
sort(fac.begin(), fac.end());
int x, flag = 0;
for(int i = 0; i < n; i++)
{
scanf("%d", &x);
int gcd = __gcd(m, x);
if(gcd == 1) flag = 1;
for(int j = 0; j < fac.size(); j++) //记录所有有贡献 的因子
if(fac[j]%gcd == 0)
cnt[j] = 1;
}
if(flag)
{
printf("Case #%d: %lld\n", ca++, (ll)m*(m-1)/2);
continue;
}
ll ans = 0;
for(int i = 0; i < fac.size(); i++)
{
if(!cnt[i]) continue;
ans += (ll)(m/fac[i]-1)*m/2*cnt[i];
for(int j = i + 1; j < fac.size(); j++)
if(fac[j]%fac[i] == 0)
cnt[j] -= cnt[i];
}
printf("Case #%d: %lld\n", ca++, ans);
}
return 0;
}