题目大意:有一棵树,每个点有点值,每条边有边权,设
g
c
d
(
u
,
v
)
gcd(u,v)
g c d ( u , v ) 表示
u
u
u 到
v
v
v 上所有点的点值的
g
c
d
gcd
g c d ,设
d
i
s
(
u
,
v
)
dis(u,v)
d i s ( u , v ) 为
u
u
u 到
v
v
v 上所有边的边权和,设
m
i
n
(
u
,
v
)
min(u,v)
m i n ( u , v ) 表示
u
u
u 到
v
v
v 上所有点的点值最小值,求最大的
g
c
d
(
x
,
y
)
∗
m
i
n
(
x
,
y
)
∗
d
i
s
(
x
,
y
)
gcd(x,y)*min(x,y)*dis(x,y)
g c d ( x , y ) ∗ m i n ( x , y ) ∗ d i s ( x , y )
这题一看就很不可做,确实有点不可做,甚至让我连标题都不知道该怎么取。干脆就直接把题目出处和名称放上去,后面的算法标签实在是标不来。
废话少说,我们观察了数据范围之后,有了一种巧妙的做法,我们每次枚举
g
c
d
gcd
g c d 的值,然后再从大到小加入每一条权值为为当前枚举的
g
c
d
gcd
g c d 倍数的边,边加入边维护当前树的直径(因为答案是
g
c
d
∗
m
i
n
∗
d
i
s
gcd*min*dis
g c d ∗ m i n ∗ d i s ,
g
c
d
gcd
g c d 已经枚举,
m
i
n
min
m i n 就是当前边的权值,而
d
i
s
dis
d i s 要最长,显然当前树的直径最长),这样统计出的答案就会小于等于最终的答案(因为可能所有边的权值都是当前枚举的
g
c
d
gcd
g c d 的倍数,那么真正的
g
c
d
gcd
g c d 就会变大),但随着
g
c
d
gcd
g c d 从小到大枚举,最终即可获得全部的答案。
可以证明,这样的复杂度是可行的(O(跑得过),因为复杂度我也不太会证,但是感性理解就是gcd的取值实际上并不多)
所以剩下的问题就是维护树的直径了,这里有一个重要的结论,就是合并两棵树
X
、
Y
X、Y
X 、 Y 时,如果
X
X
X 的直径两端点是
a
,
b
a,b
a , b ,
Y
Y
Y 的直径两端点是
c
,
d
c,d
c , d ,那么合并出来的树的直径的两端点一定是
a
,
b
,
c
,
d
a,b,c,d
a , b , c , d 四个点中的两个。所以我们枚举每种可能的情况,算出
d
i
s
dis
d i s 值最大的那种情况,那种情况的两个点就是新的直径的两个端点。
#include<bits/stdc++.h>
#define MAXN 200005
#define ll long long
using namespace std;
ll read(){
char c;ll x=0,y=1;while(c=getchar(),(c<'0'||c>'9')&&c!='-');
if(c=='-') y=-1;else x=c-'0';while(c=getchar(),c>='0'&&c<='9')
x=x*10+c-'0';return x*y;
}
void print(ll x){
if(x/10) print(x/10);
putchar(x%10+'0');
}
ll T,n,mx,cnt,ans,tot,cur,res;
ll u[MAXN],v[MAXN],c[MAXN],dep[MAXN],pos[MAXN],fa[MAXN],a[MAXN],l[MAXN];
ll head[MAXN<<1],nxt[MAXN<<1],go[MAXN<<1],s[MAXN][20],t[MAXN][20],A[MAXN],B[MAXN],lst[MAXN];
vector<ll> g[MAXN];
struct node{
ll to,val;
}L[MAXN<<1];
void add(ll x,ll y,ll c){
L[cnt]=(node){y,c};
nxt[cnt]=head[x];head[x]=cnt;cnt++;
L[cnt]=(node){x,c};
nxt[cnt]=head[y];head[y]=cnt;cnt++;
}
ll gcd(ll x,ll y){
return y?gcd(y,x%y):x;
}
void getgcd(ll x,ll y){
for(ll i=1;i*i<=x;i++)
if(x%i==0){
g[i].push_back(y);
if(x/i!=i) g[x/i].push_back(y);
}
}
ll find(ll x){
if(fa[x]!=x) fa[x]=find(fa[x]);
return fa[x];
}
void unionn(ll &x,ll &y){
x=find(x);y=find(y);fa[y]=x;
}
void dfs(ll x,ll fa){
c[++tot]=dep[x];pos[x]=tot;
for(ll i=head[x];i!=-1;i=nxt[i]){
ll to=L[i].to;
if(to==fa) continue;
dep[to]=dep[x]+L[i].val;dfs(to,x);c[++tot]=dep[x];
}
}
void pre(){
tot=0;dfs(1,0);register int i,j;
for(i=1;i<=tot;i++) s[i][0]=t[i][0]=c[i];
for(j=1;j<=17;j++)
for(i=1;i<=tot;i++){
if(i+(1<<j)-1<=tot) s[i][j]=min(s[i][j-1],s[i+(1<<j-1)][j-1]);
if(i-(1<<j)>=0) t[i][j]=min(t[i][j-1],t[i-(1<<j-1)][j-1]);
}
}
ll rmq(ll x,ll y){ //这个实际上是处理从到LCA的路径长度,注意上面的c数组的构建,这个操作很风骚
x=pos[x];y=pos[y];if(x>y) swap(x,y);
ll k=(ll)log2(y-x+1);
return min(s[x][k],t[y][k]);
}
bool cmp(ll x,ll y){
return a[u[x]]>a[u[y]];
}
void wr(ll x,ll y){ //维护直径
if(lst[x]!=cur) lst[x]=cur,A[x]=B[x]=fa[x]=x,l[x]=0;
if(lst[y]!=cur) lst[y]=cur,A[y]=B[y]=fa[y]=y,l[y]=0;
unionn(x,y);ll a1=A[x],b1=B[x];
if(l[y]>l[x]) l[x]=l[y],a1=A[y],b1=B[y];
ll num=dep[A[x]]+dep[A[y]]-2*rmq(A[x],A[y]);
if(num>l[x]){l[x]=num;a1=A[x],b1=A[y];}
num=dep[A[x]]+dep[B[y]]-2*rmq(A[x],B[y]);
if(num>l[x]){l[x]=num;a1=A[x],b1=B[y];}
num=dep[B[x]]+dep[A[y]]-2*rmq(B[x],A[y]);
if(num>l[x]){l[x]=num;a1=B[x],b1=A[y];}
num=dep[B[x]]+dep[B[y]]-2*rmq(B[x],B[y]);
if(num>l[x]){l[x]=num;a1=B[x],b1=B[y];}
A[x]=a1;B[x]=b1;res=max(res,l[x]);
}
void solve(ll x){
sort(g[x].begin(),g[x].end(),cmp);
cur++;res=0;
for(ll i=0;i<g[x].size();i++){
ll p=g[x][i];
wr(u[p],v[p]);
ans=max(ans,res*x*a[u[p]]);
}
}
int main()
{
T=read();register int i;
while(T--){
n=read();ans=0;cnt=0;mx=0;
memset(head,-1,sizeof(head));
for(i=1;i<=n;i++) a[i]=read(),mx=max(mx,a[i]);
for(i=1;i<=mx;i++) g[i].clear();
for(i=1;i<n;i++){
ll x=read(),y=read(),c=read();
add(x,y,c);u[i]=x;v[i]=y;
if(a[u[i]]>a[v[i]]) swap(u[i],v[i]);
getgcd(gcd(a[u[i]],a[v[i]]),i);
}
pre();
if(T==34){
int ubt=0;
}
for(i=1;i<=mx;i++)
solve(i);
print(ans);puts("");
}
return 0;
}