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;
}
posted @ 2026-06-30 19:21  kzssCCC  阅读(6)  评论(0)    收藏  举报