CF1986G2 思路分享(数论)
https://codeforces.com/contest/1986/problem/G2
题意
给定长度为 \(n\) 的排列 \(p\),求满足:
-
\(1\le i\lt j\le n\)
-
\((i\cdot j)\mid (p_i \cdot p_j)\)
的对数.
\(1\le n\le 5\cdot 10^5\).
思路
记 \(a_i = \frac{p_i}{\gcd(p_i,i)}\),\(b=\frac{i}{\gcd(p_i,i)}\),则 \((i\cdot j)\mid (p_i \cdot p_j)\) 等价于 \(b_j \mid a_i\) 且 \(b_i \mid a_j\).
首先预处理 \(refa_x\) 存所有 \(a_i = x\) 的 \(i\),\(refb_x\) 存所有 \(b_i=x\) 的 \(i\).
枚举 \(b_i\),然后枚举 \(a_j\) 为 \(b_i\) 的倍数,这样固定了 \(b_i\) 和 \(a_j\) 并约束了 \(b_i \mid a_j\),对应的 \(a_i\) 和 \(b_j\) 也确定,此时需要约束 \(b_j \mid a_i\).
在讨论 \(a_i\) 时,我们只关心 \(b_j\),可以用 \(cnt\) 数组记录:\(b_i \mid a_j\) 情况下 \(b_j\) 出现的次数,然后枚举 \(a_i\) 的因数 \(d\),累加 \(cnt_d\) 即可.
预处理因数,实际有效的操作数量为两个调和级数之和.
时间复杂度 \(\mathcal{O}(n\log n)\).
代码
//author:kzssCCC
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int MAXN = 5e5;
vector<int> fac[MAXN+1];
void init(){
for (int i=1;i<=MAXN;i++){
for (int j=i;j<=MAXN;j+=i){
fac[j].push_back(i);
}
}
}
void solve(){
int n;
cin >> n;
vector<int> p(n+1),a(n+1),b(n+1);
vector<vector<int>> refa(n+1),refb(n+1);
for (int i=1;i<=n;i++){
cin >> p[i];
a[i] = p[i]/__gcd(p[i],i);
b[i] = i/__gcd(p[i],i);
refa[a[i]].push_back(i);
refb[b[i]].push_back(i);
}
ll res = 0;
vector<int> cnt(n+1);
vector<int> wait;
for (int p=1;p<=n;p++){
for (int q=p;q<=n;q+=p){
for (auto& j:refa[q]){
if (cnt[b[j]]++==0){
wait.push_back(b[j]);
}
}
}
for (auto& i:refb[p]){
for (auto& f:fac[a[i]]){
res += cnt[f];
}
}
for (auto& v:wait){
cnt[v] = 0;
}
wait.clear();
}
for (int i=1;i<=n;i++){
if (a[i]%b[i]==0){
res--;
}
}
cout << res/2 << '\n';
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
init();
int t = 1;
cin >> t;
while (t--) solve();
return 0;
}

浙公网安备 33010602011771号