2015ICPC-沈阳-F

2015ICPC-沈阳-F

七月 12, 2020

F.Frog

题意

n个青蛙m个石子 下标从0到m-1

每只青蛙一次条ai格 假如它在x处下次它会在 (ai+x)%m 处

让这n只青蛙一直跳 求被青蛙跳到的石子下标和 被跳过多次只计算1次

题解

不难发现 对于某只青蛙而言它能跳到的石头一定是gcd(a[i],m)的倍数

而对于某块石头x而言若存在gcd(a[i],m)是x的因子 即 gcd(a[i],m)|m 那它就会被跳到

那我们对于gcd(a[i],m)=g,它产生的贡献是:

g ×(首项+末项)× (项数)/2=g×(1+m/g-1)×(m/g-1)/2=m×(m/g-1)/2

那么问题来了,被重复计算的怎么减掉呢?

由于gcd(a[i],m)一定是m的因子集合的子集 我们可以用它的因子来计算贡献

先求出m的所有因子x,存入phi中,再判断这些因子x中哪些是gcd(a[i],m)的倍数

cnt[x]=1 表示第x个因子是gcd(a[i],m)的倍数 要被计算到贡献中1次

最后在遍历所有因子 求它们对ans的贡献

对于第x个因子它产生的贡献是 :m × (m/phi[x]-1)/2 ×(cnt[x]-repeat[x])

repeat[x]表示是第x个因子倍数的石子已经被重复计算过的次数 要从贡献中减去

每次求完这个因子x的贡献后 for循环找出比它大的因子y中有哪些是因子x的倍数

​并更新 repeat[y]+=(cnt[x]-repeat[x]) 表示因子y的贡献被它更小的因子x已经多计算了

(cnt[x]-repeat[x]) 次 这部分就是容斥原理的体现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define endl '\n'
#define Turnoff std::ios::sync_with_stdio(false)
typedef pair<ll,ll> P;

const int mod=2e3;
const int Max=1e5+5;
int gcd[Max],cnt[Max],repeat[Max];

int main(){
Turnoff;
int t,ti=1;cin>>t;
while(t--){
vector<ll>phi;
memset(cnt,0,sizeof cnt);
memset(repeat,0,sizeof repeat);
ll n,m;cin>>n>>m;
for(int i=0;i<n;i++){
ll temp;cin>>temp;
gcd[i]=__gcd(temp,m);
}
phi.push_back(1);
for(int i=2;i*i<=m;i++){
if(m%i)continue;
phi.push_back(i);
if(i*i!=m)phi.push_back(m/i);
}
sort(phi.begin(),phi.end());
for(int i=0;i<n;i++){
for(int j=0;j<phi.size();j++){
if(phi[j]%gcd[i])continue;
cnt[j]=1;
}
}
ll ans=0;
for(int i=0;i<phi.size();i++){
if(!cnt[i])continue;
ans+=m*(m/phi[i]-1)/2*(cnt[i]-repeat[i]);
for(int j=i+1;j<phi.size();j++){
if(phi[j]%phi[i])continue;
repeat[j]+=(cnt[i]-repeat[i]);
}
}
cout<<"Case #"<<ti++<<": "<<ans<<endl;
}
}

为什么不能直接通过枚举gcd(a[i],m)容斥直接计算贡献 就是用gcd(a[i],m)组成的集合来计算ans呢

不加入求m的因子集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define endl '\n'
#define Turnoff std::ios::sync_with_stdio(false)
typedef pair<ll,ll> P;

const int mod=2e3;
const int Max=1e5+5;
int gcd[Max],cnt[Max],repeat[Max];

int main(){
Turnoff;
int t,ti=1;cin>>t;
while(t--){
vector<ll>phi;
memset(repeat,0,sizeof repeat);
ll n,m;cin>>n>>m;
for(int i=0;i<n;i++){
ll temp;cin>>temp;
gcd[i]=__gcd(temp,m);
cnt[i]=1;
}
sort(gcd,gcd+n);

ll ans=0;
for(int i=0;i<n;i++){
ans+=m*(m/gcd[i]-1)/2*(cnt[i]-repeat[i]);
for(int j=i+1;j<n;j++){
if(gcd[j]%gcd[i])continue;
repeat[j]+=(cnt[i]-repeat[i]);
}
}
cout<<"Case #"<<ti++<<": "<<ans<<endl;
}
}
/*
3
2 12
9 10
3 60
22 33 66
9 96
81 40 48 32 64 16 96 42 72
*/
///对于第一个样例正确答案是42 而不用m的因子的方法得到的答案是48
///因为6 是gcd(10,12)=2的倍数也是gcd(9,12)=3的倍数 若用这种方法无法消除被重复计算的石子的贡献
///而用m的因子集合来求贡献就一定可以 因为gcd(a[i],m)的集合是因子集合的子集
///能被跳到的石子一定是m的因子 所以6会被计入因子集合而 gcd的集合中不包括它

此外还有一种分析

因为gcd(x,m)|m 所以枚举m的因子 假如对于m的一个因数k,如果存在一个i使得k能被ai整除(能被跳到),则此时对于所有石头x符合gcd(x,m)==k的都对答案产生贡献:

∑x,{gcd(x,m)=k} = k*∑x/k,{gcd(x/k,m/k)=1}

∑x/k,{gcd(x/k,m/k)=1} = φ(m/k)*(m/k)/2

这里用到一个结论小于n于n互质的数的和 sum=φ(n)*n/2

推倒 假设一个数xi与n互质 那么 n-xi也与n互质 (更相减损法求gcd)

所以 sum=(x1 +n-x1)+(x2+n-x2)+…..(xφ(n)+n-xφ(n))/2

​除2是因为每个与n互质的数都被枚举了2次即(n-xi)=xj

参考博客