[莫反] [点分治] CF990G GCD Counting
posted on 2024-03-27 13:31:51 | under | source
听老师说可以直接莫反?试试看。
尝试单独分析每个询问 \(d\)。定义 \(S(i,j)\) 表示 \(i\to j\) 简单路径的 \(\gcd\) 之和,那问的就是 \(\sum\limits_i \sum\limits_j [S(i,j)=d]\)。
开始推式子:
\(=\sum\limits_i \sum\limits_j [\frac{S(i,j)}{d}=1]\)
\(=\sum\limits_i \sum\limits_j \sum\limits_{pd\mid S(i,j)}\mu(p)\)
套路地提出乘积项:
\(=\sum\limits_{d\mid T} \mu(\frac Td)\sum\limits_i\sum\limits_j [T\mid S(i,j)]\)
定义 \(H(T)=\sum\limits_i\sum\limits_j [T\mid S(i,j)]\):
\(=\sum\limits_{d\mid T} H(T)\mu(\frac Td)\)
那么每次询问枚举 \(T\) 即可,现在地问题转化为 \(H(T)\) 怎么求。
显然 \(T\mid S(i,j)\) 等价于 \(T\mid a_k\),其中 \(k\) 指 \(i\to j\) 简单路径上的点。于是进行 \(\rm dfs\),每次枚举当前点权的约数,暴力合并信息即可。
\(A\) 是点权值域。对每个因数单独建树,在新的树上统计,复杂度 \(O(n\sqrt A)\)。
总复杂度:\(O(n\sqrt A + A\log A)\)。
代码
其实没必要真的建树的说。
然后注意下不要把所有变量赋为 long long,否则会爆内存。
#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 2e5 + 5;
int pc, prime[N], f[N], mu[N], u, v;
int n, vis[N], a[N];
LL h[N], cnt;
vector<int> d[N], to[N];
inline void init(){
f[1] = mu[1] = 1;
for(int i = 2; i < N; ++i){
if(!f[i]) {prime[++pc] = i, mu[i] = -1;}
for(int j = 1; j <= pc && i * prime[j] < N; ++j){
f[i * prime[j]] = 1, mu[i * prime[j]] = i % prime[j] ? -mu[i] : 0;
if(i % prime[j] == 0) break;
}
}
}
inline void dfs(int u, int fa, int d){
++cnt, vis[u] = 1;
for(auto v : to[u])
if(!vis[v] && a[v] % d == 0) dfs(v, u, d);
}
signed main(){
init();
cin >> n;
for(int i = 1; i <= n; ++i){
scanf("%d", &a[i]);
for(int j = 1; j * j <= a[i]; ++j)
if(a[i] % j == 0){
d[j].push_back(i);
if(j * j != a[i]) d[a[i] / j].push_back(i);
}
}
for(int i = 1; i < n; ++i) scanf("%d%d", &u, &v), to[u].push_back(v), to[v].push_back(u);
for(int i = 1; i < N; ++i){
for(auto j : d[i])
if(!vis[j])
cnt = 0, dfs(j, 0, i), h[i] += cnt * (cnt - 1) / 2 + cnt;
for(auto j : d[i]) vis[j] = 0;
}
for(int i = 1; i < N; ++i){
LL res = 0;
for(int j = i; j < N; j += i) res += h[j] * mu[j / i];
if(res) printf("%d %lld\n", i, res);
}
return 0;
}

浙公网安备 33010602011771号