容斥原理

线性容斥

D. Counting Rhyme

题意

题目大意:有一个长度为n的数组a,如果对于一个数个(a[i],a[j])满足不存在a[k]使a[i]%a[k]=0且a[j]%a[k]=0,则称这个数对是合法的,求合法数对的数量。

思路

  1. 对于任意一个a[k],如果这个数合法,则**没有点对(i,j)**,满足$gcd(a[i],a[j])%a[k]=0$

  2. 考虑单点贡献,因为点对贡献是$O(n^2)$的复杂度

  3. 考虑dp,设$dp[g]$表示$gcd(a[i],a[j])=g$的点对数,则$ans=\sum_{a_k\nmid g\forall k\in[1,n]}dp_g$

  4. 具体如何求$dp[g]$:如果我们知道一个多重集合中,任意两个数的gcd都是g的倍数,那么就好做了

    dp[g]=C(cnt,2)-dp[2*g]-dp[3*g]-…dp[k*g](C表示组合数,cnt表示集合中的多重集中元素个数)

代码

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
50
51
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+5;
ll dp[N];
ll a[N];
ll cnt[N];
ll n;
bool biaoji[N];
void solve()
{
cin>>n;
for(int i=1;i<=n;i++){
dp[i]=cnt[i]=biaoji[i]=0;
}
for(int i=1;i<=n;i++){
cin>>a[i];
cnt[a[i]]++;
}
ll zong=0;
for(ll g=n;g>=1;g--){
ll sum=cnt[g];
for(ll k=2;k*g<=n;k++){
dp[g]-=dp[k*g];
sum+=cnt[k*g];
}
dp[g]+=sum*(sum-1)/2;
}
for(ll i=1;i<=n;i++){
if(!cnt[i])continue;
for(ll j=1;j*i<=n;j++){
biaoji[j*i]=1;//有存在的a[k]使得这个gcd做不了贡献!!
}
}
ll ans=0;
for(int i=1;i<=n;i++){
if(!biaoji[i]){
ans+=dp[i];
}
}
cout<<ans<<endl;
}
int main()
{
int t=0;
cin>>t;
while(t--){
solve();
}
return 0;
}

注意,最后要算点对数,而不是a[k]的数目,所以关键是判断哪些点对合法。

而dp[g]就是表示gcd恰好等于g的所有点对数,所以只需要判断哪些g合法即可


容斥原理
http://example.com/2023/11/30/算法/容斥原理/
作者
Mrxiad
发布于
2023年11月30日
许可协议