题目大意是给定一些(n)青蛙及其跳跃的步数和方法,给定一些石头,编号从1~m,问所有曾经被青蛙跳过的石头的编号的和。
思路
容斥定理
从题目中很容易得出每一个青蛙的跳过的石头编号是k * gcd(m, frogi) {m/ gcd(m, frogi) >= k >= 1},这样用等差数列求和公式就能求出
每一个青蛙所跳过的石头编号的和。但是有一些石头是被重复计算的。这样就用到了容斥原理
wiki百科 https://en.wikipedia.org/wiki/Inclusion%E2%80%93exclusion_principle
训练时没有做出来,参考了大牛的思路。http://www.cnblogs.com/Quinte/p/4932471.html
可以先把m所有的因子求出来,排好序。 对于每一个是青蛙跳过的石头编号的倍数的因子进行标记,表示该因子做了贡献,
做贡献的次数用一个数组或者map保存起来,然后把所有因子从小到大进行扫描,如果当前因子要做贡献,则进行加和,同
时把所有是该因子倍数的因子要加和的次数减去当前因子做贡献的次数。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <set>
#include <vector>
#include <map>
#include <cmath>
using namespace std;
typedef long long LL;
const int maxn = 100000 + 10;
int n, m;
int f[maxn];
bool vis[maxn];
map<int, int> times;
int gcd(int a, int b){
return a == 0 ? b : gcd(b % a ,a);
}
LL solve(){
LL sum = 0;
vector<int> fact;
times.clear();
memset(vis, false, sizeof vis);
for(int i = 1; i <= int(sqrt(m) + 0.5); ++i){
if(m % i == 0) {fact.push_back(i); times[i] = 0;
if(i * i != m) {fact.push_back(m / i); times[m / i] = 0;}
}
}
sort(fact.begin(), fact.end());
for(int i = 0; i < n; ++i){
int x = gcd(f[i], m);
for(int j = 0; j < fact.size(); ++j){
if(fact[j] % x == 0) vis[j] = true;
}
}
fact.pop_back();
//Inclusion_exclusion principle
for(int i = 0; i < fact.size(); ++i){
if((int)vis[i] != times[i]){
int t = m / fact[i];
sum += (LL)t * (t - 1) * fact[i] / 2 * ((int)vis[i] - times[i]);
t = vis[i] - times[i];
for(int j = i + 1; j < fact.size(); ++j){
if(fact[j] % fact[i] == 0) times[j] += t;
}
}
}
return sum;
}
int main()
{
int T; scanf("%d", &T);
int kase = 0;
while(T --){ scanf("%d %d", &n, &m);
for(int i = 0; i < n; ++i ){
scanf("%d",&f[i]);
}
printf("Case #%d: %I64d\n", ++kase, solve());
}
return 0;
}