Frogs HDU - 5514 (容斥定律)
题面:
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5514
题意:
给定(mathit m)个石头和(mathit n) 个青蛙,石头标号为(0,1,dots,m-1)。最开始所以青蛙都在标号为( ext 0)的石头上,然后每一个青蛙每一次跳(a_i)个石头,即如果当前在标号为(mathit j) 的石头上,一次跳跃后在标号为((j+a_i)~mod~m)的石头上,每一个青蛙跳无数次,问你被青蛙到达过的石头的标号总和是多少?
思路:
很容易知道第(mathit i)个青蛙会经过这些石头(k*gcd(a_i,m)),其中(kin[0,frac{m}{gcd(a_i,m)}]),这些石头的标号是一个等差数列,可以通过等差数列求和公式快速求出。
但是不同的青蛙路过的石头会有重复的,如何去除重复计算的是本题的关键点。
我们知道(gcd(a_i,m))一定是(mathit m) 的因子,那么我们不妨先将(mathit m)进行因子分解,
再用一个辅助数组(vis_i)代表因子(mathit i) 以及它的倍数数列应该对答案的贡献系数。
那么能正确的处理出数组(vis),让其每个数不被重复计算和少计算即可通过系数乘以等差数列的和得出正确答案。
处理数组的方法:
最初让(mathit m)的因子中是(gcd(a_i,m))的倍数的数(mathit x) ,处理为(vis[x]=1)。
我们将所有因子从小到大排序后,从小开始遍历每一个因子(mathit x) ,
对答案加上(vis[x]*cal(x)),其中(cal(x))就是等差数列(k*x)的和。
然后枚举所有(mathit m)的因子中大于(mathit x) 的且是(mathit x) 的倍数的(mathit y),
让(vis[y]-=vis[x]),即因为(x|y),所以(mathit y)的倍数已经被(mathit x) 给算过一次了,(mathit y) 对应的系数就应该减去(vis[x])。
这样最终得出的结果就是正确的了。
代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
typedef long long ll;
int T, n, m, tot;
int a[N], vis[N];
int gcd(int a, int b)
{
return b == 0 ? a : gcd(b, a % b);
}
ll ans;
ll cal(ll x)
{
ll p = (m - 1) / x + 1;
return p * (p - 1) * x / 2;
}
int main()
{
//freopen("in.txt","r",stdin);
scanf("%d", &T);
for (int cas = 1; cas <= T; cas++) {
memset(vis, 0, sizeof(vis));
ans = tot = 0;
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
a[i] = gcd(a[i], m);
}
sort(a + 1, a + n + 1);
n = unique(a + 1, a + n + 1) - a - 1;
std::vector<int> v;
for (int i = 1; i * i <= m; ++i) {
if (m % i == 0) {
v.push_back(i);
if (i * i != m) {
v.push_back(m / i);
}
}
}
sort(v.begin(), v.end());
int k = v.size();
for (int j = 0; j < k; ++j) {
for (int i = 1; i <= n; i++) {
if (v[j] % a[i] == 0) {
vis[j] = 1;
}
}
}
for (int j = 0; j < k; ++j) {
ans += cal(v[j]) * vis[j];
for (int i = j + 1; i < k; ++i) {
if (v[i] % v[j] == 0) {
vis[i] -= vis[j];
}
}
}
printf("Case #%d: %lld
", cas, ans);
}
return 0;
}