题意:n个点的树,每个点的价值为a[i],
n<=1e4,a[i]<=500. 求所有(a[i],a[j])互质的点的距离和
树上路径求和的问题,从边的角度来考虑.计算u-v这条边总共走了多少次.
设dp[v][j] 子树v中,值为j的个数, 可以枚举j = [1:500] 若子树v上方有num个数和j互质,那么贡献 num*dp[v][j]
直接枚举的话 O(n*[ai]^2) TLE...
因为a[i]<=500 那么每个数最多只有4个素因子,考虑gcd(j,x)!=1的个数.
n<=1e4,a[i]<=500. 求所有(a[i],a[j])互质的点的距离和
树上路径求和的问题,从边的角度来考虑.计算u-v这条边总共走了多少次.
设dp[v][j] 子树v中,值为j的个数, 可以枚举j = [1:500] 若子树v上方有num个数和j互质,那么贡献 num*dp[v][j]
直接枚举的话 O(n*[ai]^2) TLE...
因为a[i]<=500 那么每个数最多只有4个素因子,考虑gcd(j,x)!=1的个数.
算出子树v上方gcd为p[j]的倍数有多少个. 容斥搞一搞即可.O(n*a[i]*k*2^k) k<=3)
#include <bits/stdc++.h> using namespace std; const int N=2e4+5,M=505; int n,a[N]; vector<int> p[M],e[N]; void init() { for(int i=1;i<=500;i++) { int x=i; for(int j=2;j*j<=i;j++) { if(x%j==0) { p[i].push_back(j); while(x%j==0) x/=j; } } if(x!=1) p[i].push_back(x); } } int dp[N][M],res=0;//dp[u][j] 子树u中 值为j的个数. int cnt[M];//cnt[x] 为素因子x倍数的顶点数 int sum[N][M];//sum[u][j] 子树u中 为j倍数的个数. void dfs(int u,int fa) { int x=a[u]; dp[u][x]++; for(int i=0;i<(1<<p[x].size());i++) { int num=1; for(int j=0;j<p[x].size();j++) { if((i>>j)&1) num*=p[x][j]; } sum[u][num]++; } for(int i=0;i<e[u].size();i++) { int v=e[u][i]; if(v==fa) continue; dfs(v,u); for(int j=1;j<=500;j++) { dp[u][j]+=dp[v][j]; sum[u][j]+=sum[v][j]; } //u-v 这条边贡献: dp[v][j] * num(v上半部分和j互质的个数) for(int j=1;j<=500;j++) { for(int s=0;s<(1<<p[j].size());s++) { int ct=0,num=1; for(int k=0;k<p[j].size();k++) { if((s>>k)&1) ct++,num*=p[j][k]; } if(ct%2) res-=dp[v][j]*(cnt[num]-sum[v][num]); else res+=dp[v][j]*(cnt[num]-sum[v][num]); } } } } int main() { ios::sync_with_stdio(false); cin.tie(0); init(); cin>>n; for(int i=1;i<=n;i++) { cin>>a[i]; int x=a[i]; for(int s=0;s<(1<<p[x].size());s++) { int num=1; for(int k=0;k<p[x].size();k++) { if((s>>k)&1) num*=p[x][k]; } cnt[num]++; } } for(int i=1;i<=n-1;i++) { int u,v; cin>>u>>v; e[u].push_back(v); e[v].push_back(u); } dfs(1,0); cout<<res<<'\n'; return 0; }