CF2241E
solutions
首先,考虑三个点构成的路径集有且仅有以下两种情况:
- 链
- 路径两两不交,且\(LCA(u,v,w)=x\)。
如下图所示:

对于链的情况,仅有中间的这个节点被覆盖了奇数次,因此乘积是否为完全平方数仅与 \(v\) 节点有关。不妨枚举 $ v$ ,然后在两颗不同的子树选取这样的 \(v,w\) 。这坨东西的贡献实际是好算的。设当前 \(v\) 是整颗树的根,子节点个数为\(n\) 个。
考虑正着算:\(\sum_{i=1}^n \sum_{j=i+1}^n sz_i\times sz_j\),这个东西是很好前缀和优化的。
考虑容斥算,那就是从其他节点随便选两个,去除掉在一颗子树的贡献。那就是\(\binom{n-1}{2}-\sum_{i=1}^n \binom{sz_i}{2}\)。
而对于第二种情况,仍然钦定 \(x\) 为根,那么答案就是 \(\sum_{i=1}^n \sum_{j=i+1}^n \sum_{k=j+1}^n sz_i\times sz_j\times sz_k\),依旧是可以前缀和优化的。
于是我们是有 \(O(n)\) 的做法的。
#include<bits/stdc++.h>
#define pb push_back
#define fi first
#define se second
#define mt make_tuple
using namespace std;
typedef pair<int,int>pii;
typedef tuple<int,int,int>ti3;
typedef tuple<int,int,int,int>ti4;
typedef tuple<int,int,int,int,int>ti5;
using ll=long long;
const int N=1e6+3;
bool is_sq[N];
void init(){
for(ll i=1;i*i<=N;i++){
is_sq[i*i]=true;
}
}
ll C3(ll x){
if(x<=2)return 0;
return 1ll*x*(x-1)*(x-2)/6;
}
ll C2(ll x){
if(x<=1)return 0;
return x*(x-1)/2;
}
void Silverwolf(){
int n;
cin>>n;
vector<int>a(n+1);
vector<vector<int>>e(n+1);
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<n;i++){
int u,v;
cin>>u>>v;
e[u].pb(v);
e[v].pb(u);
}
ll ans=0;
//第一种情况 链
vector<int>sz(n+1,0);
auto dfs=[&](auto &&dfs,int u,int f)->void{
sz[u]=1;
vector<int>vt;
for(int v:e[u]){
if(v==f)continue;
dfs(dfs,v,u);
sz[u]+=sz[v];
if(is_sq[a[u]])ans-=C2(sz[v]);
vt.push_back(sz[v]);
}
vt.push_back(n-sz[u]);
if(is_sq[a[u]]){
ans-=C2(n-sz[u]);
ans+=C2(n-1);
if(vt.size()>=3){
vector<ll>pre(vt.size(),0);//先算前缀和
vector<ll>pre2(vt.size(),0);//然后算第二坨东西
for(int i=0;i<vt.size();i++){
if(i==0){
pre[i]=vt[i];
}else pre[i]=pre[i-1]+vt[i];
if(i>=1)pre2[i]=pre2[i-1]+vt[i]*pre[i-1];
if(i>=2)ans+=1ll*vt[i]*pre2[i-1];
}
}
}
};
dfs(dfs,1,0);
cout<<ans<<'\n';
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
init();
// freopen("Straps.in","r",stdin);
// freopen("Straps.out","w",stdout);
int T;cin>>T;while(T--)
Silverwolf();
return 0;
}
浙公网安备 33010602011771号