SkyStarOfficial
2024-11-19 20:41:19
树上
因为每个质因子无论出现多少次,我们都把它们看做一次,所以我们认为它们对答案的贡献是独立的。
那么我们现在仅考虑一种质因子。
因为它是联通块,
我们现在需要考虑一条边对于
它所包含的点可以是除了端点之外的任意一个合法的点,共有
这些联通块每个都会包含这条边,边
所以这种质因子对答案贡献是
我们需要求出每个点两两之间的距离,这个可以通过树形 DP 实现。
假设我们已经求出了一个点子树内包含同种质因子的点有
这时候我们发现如果质因子太大的话可以最多需要遍历
可以证明一个数的质因子不超过
就可以使用虚树维护每种质因子。
但这种方法比较复杂,考虑一种比较简单的树上启发式合并的简单做法。
每次我们把小的子节点向大的节点合并,如果发现根节点较小就交换,这样保证了最多合并
我们记录每个子节点的质因数,和这个质因子一共出现的次数,那么它的系数就是
我们合并时每次减掉旧的贡献,加上新的贡献即可。
注意一个节点的初始贡献是只包含这个点的情况。
精细化实现复杂度为
#include<bits/extc++.h>
using namespace __gnu_pbds;
#define int long long
using namespace std;
const int MAXN=3e5+10;
const int mod=998244353;
gp_hash_table<int,int>mp[MAXN];
int n,colcnt[MAXN],res[MAXN],ans=0,inv;
vector<int>col[MAXN];
struct node{
int nxt,to;
}e[MAXN*4];
int head[MAXN],tot=0;
void add(int x,int y){
e[++tot].nxt=head[x];
e[tot].to=y;
head[x]=tot;
}
int qpow(int base,int ret){
int ans=1;
while(ret){
if(ret&1)ans=ans*base%mod;
base=base*base%mod;
ret/=2;
}
return ans;
}
void dfs(int x,int fa){
for(int i=head[x];i;i=e[i].nxt){
int v=e[i].to;
if(v==fa)continue;
dfs(v,x);
if(mp[x].size()<mp[v].size()){
swap(mp[x],mp[v]);swap(res[x],res[v]);
}
for(auto i:mp[v]){
res[x]-=(colcnt[i.first]-2)*mp[x][i.first]%mod*(colcnt[i.first]-mp[x][i.first])%mod*inv%mod;
res[x]=(res[x]+mod)%mod;
mp[x][i.first]+=i.second;
res[x]+=(colcnt[i.first]-2)*mp[x][i.first]%mod*(colcnt[i.first]-mp[x][i.first])%mod*inv%mod;
res[x]=(res[x]+mod)%mod;
}
}
ans+=res[x];
ans%=mod;
}
signed main(){
ios::sync_with_stdio(0);
cin>>n;inv=qpow(2,mod-2);
for(int k=1;k<=n;k++){
int x;cin>>x;
for(int i=2;i<=sqrt(x);i++){
if(x%i==0){
col[k].emplace_back(i);
colcnt[i]++;
}
while(x%i==0)x/=i;
}
if(x!=1)col[k].emplace_back(x),colcnt[x]++;
}
for(int i=1;i<n;i++){
int x,y;cin>>x>>y;add(x,y);add(y,x);
}
for(int i=1;i<=n;i++){
for(auto j:col[i]){
mp[i][j]++;
res[i]+=(colcnt[j]-2)*(colcnt[j]-1)%mod*inv%mod;
res[i]%=mod;
}
}
dfs(1,0);
cout<<ans%mod;
return 0;
}