[数论] [计数] 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;
}

浙公网安备 33010602011771号