[数论] [计数] CF1986G2 Permutation Problem (Hard Version)

posted on 2024-07-02 05:52:26 | under | source

直接统计是困难的,考虑简化条件,令 \(p_i,i\) 除以它们的最大公因数,分别得到 \(a_i,b_i\),这样的好处是 \(a,b\) 互质。因此,\(\frac{a_ia_j}{b_ib_j}\) 为整数当且仅当 \(b_j\mid a_i\)\(b_i\mid a_j\)

\(d(n)\)\(n\) 的因数个数。显然 \(\sum d(b_i)=\sum d(p_i)=O(n\ln n)\),由于 \(a_i\mid p_i\),所以也有 \(\sum d(a_i)=O(n\ln n)\)。利用这一点统计答案。

具体而言,枚举值域。枚举数对 \(b\mid a\),然后用桶 \(buc\) 统计 \(a_j=a\)\(b_j\) 个数;最后对 \(b_i=b\)\(a_i\) 大力枚举其因数 \(d\),贡献就是 \(buc_d\)

上述过程可以视为两部分,由先前的讨论,每一部分的复杂度都是 \(O(n\ln n)\) 的,所以总复杂度 \(O(n\ln n)\)。注意下循环顺序不然复杂度会退化。

代码

#include<bits/stdc++.h>
using namespace std;

const int N = 5e5 + 5;
int T, n, a[N], b[N], buc[N];
long long ans;
vector<int> x[N], y[N], ys[N]; 

signed main(){
	cin >> T;
	while(T--){
		scanf("%d", &n);
		ans = 0;
		for(int i = 1; i <= n; ++i) x[i].clear(), y[i].clear(), ys[i].clear();
		for(int i = 1; i <= n; ++i){
			scanf("%d", &a[i]), b[i] = i;
			int d = __gcd(a[i], b[i]);	a[i] /= d, b[i] /= d;		
			x[b[i]].push_back(a[i]), y[a[i]].push_back(b[i]); 
			if(a[i] % b[i] == 0) --ans;
		}
		for(int i = 1; i <= n; ++i)
			for(int j = i; j <= n; j += i) ys[j].push_back(i); //预处理因数 
		for(int i = 1; i <= n; ++i){
			for(int j = i; j <= n; j += i)
					for(auto _s : y[j]) ++buc[_s];
			for(auto s : x[i])	
				for(auto _s : ys[s]) ans += buc[_s];
			for(int j = i; j <= n; j += i)
				for(auto _s : y[j]) --buc[_s];
		}
		printf("%lld\n", ans / 2);
	}
	return 0;
}
posted @ 2026-01-13 11:16  Zwi  阅读(0)  评论(0)    收藏  举报