Part 0
怎样一个数是立方数。如果一个数是立方数,那么它分解质因数之后,每种质因子的出现次数都是三的倍数。
由于所有数只由K个质数组成,而K的大小不超过30,那么每个数就可以表示成一个三进制数来表示每种质因子在其的质因子中出现次数模3的余数。然后一个立方数就是表示其的三进制数为0,然后知道一个数的三进制表示,就可以知道什么与它相乘可以得到立方数。
Part 1
由于本题是树的路径题,不难想到点分治。由于取出的两条链可能属于根同一个子节点的子树内,这次该如何处理呢。
首先想一想我们如何用树形DP解决求树的直径这道题的。——————先更新答案,再更新DP数组。这样每次处理就不会遇到取出的两条链属于根的同一个子节点的子树内。
我们可以使曾经处理过的链都是以当前的根为端点,而当前处理的链都是以当前的根的一个子节点为端点的,这样两条链一合并就是一条完整的经过当前枚举的根的路径。
我们这里也是这样解决的。(不能理解的仔细的想一想,慢慢理解一下,我也是这样过来的。)
于是代码就变成了酱紫,变化最大的就是dfs函数。
#include<cstdio>
#include<map>
#include<algorithm>
#include<cstring>
#define M 50005
using namespace std;
void check_max(int &x,int y){if(x<y)x=y;}
struct E{
int to,nx;
}edge[M<<1];
int tot,head[M];
void Addedge(int a,int b){
edge[++tot].to=b;
edge[tot].nx=head[a];
head[a]=tot;
}
long long Prime[35];
int m,ans;
long long val[M];
long long V[M];
void Change(long long v,long long &num){//把一个数质因数分解并用每个质因数个数装压成三进制表示这个数
num=0;
for(int i=1;i<=m;i++){
int cnt=0;
if(v%Prime[i]==0)while(v%Prime[i]==0)v/=Prime[i],cnt++;
cnt%=3;
num=num*3+cnt;
}
}
bool mark[M];
int sz[M],mx_sz[M],Root,tot_sz;
void Get_Root(int now,int fa){
sz[now]=1,mx_sz[now]=0;
for(int i=head[now];i;i=edge[i].nx){
int nxt=edge[i].to;
if(nxt==fa||mark[nxt])continue;
Get_Root(nxt,now);
sz[now]+=sz[nxt];
check_max(mx_sz[now],sz[nxt]);
}
check_max(mx_sz[now],tot_sz-sz[now]);
if(mx_sz[now]<mx_sz[Root]||Root==0)Root=now;
}
map<long long,int>res,dp;//res是已经操作过的子树内的信息,dp存的是当前正在操作的子树的信息
//map就相当于一个计数的数组
map<long long,int>::iterator it;
long long Plus(long long x,long long y){//两个数的相乘时他们对应三进制数的合并
long long res=0,Base=1;
while(x||y){
res+=1ll*((x%3)+(y%3))%3*Base;
Base*=3;
x/=3,y/=3;
}
return res;
}
long long Div(long long x){//求一个三进制数和那种三进制数合并会是0,也就是找这个数乘上哪个数会得到一个立方数
long long res=0,Base=1;
while(x){
res+=1ll*(3-(x%3))%3*Base;
Base*=3;
x/=3;
}
return res;
}
void Get_dp(int now,int fa,long long pre){//把当前子树内的一个端点为这个子节点的链的信息收集
if(dp.find(pre)!=dp.end())dp[pre]++;
else dp[pre]=1;
for(int i=head[now];i;i=edge[i].nx){
int nxt=edge[i].to;
if(nxt==fa||mark[nxt])continue;
Get_dp(nxt,now,Plus(pre,V[nxt]));
}
}
void dfs(int now){
if(V[now]==0)ans++;
res.clear();//清空
res[V[now]]=1;//这个是将一个端点就是这个根的情况判进去
mark[now]=1;
for(int i=head[now];i;i=edge[i].nx){
int nxt=edge[i].to;
if(mark[nxt])continue;
dp.clear();//注意要清空
Get_dp(nxt,now,V[nxt]);
for(it=dp.begin();it!=dp.end();++it){//先更新答案
long long Find=Div(it->first);
if(res.find(Find)!=res.end())ans+=res[Find]*(it->second);
}
for(it=dp.begin();it!=dp.end();++it){
long long add=Plus(it->first,V[now]);//使res内存的都是以当前的根为一个端点的链的信息
if(res.find(add)!=res.end())res[add]+=it->second;//再更新dp数组
else res[add]=it->second;
}
}
for(int i=head[now];i;i=edge[i].nx){//等处理完经过当前的根的路径之后再dfs下去
int nxt=edge[i].to;
if(mark[nxt])continue;
tot_sz=sz[nxt],Root=0;
Get_Root(nxt,now),dfs(Root);
}
}
void Init(){
ans=tot=0;
memset(head,0,sizeof(head));
memset(mark,0,sizeof(mark));
}
int main(){
int n;
while(~scanf("%d",&n)){
Init();//清空
scanf("%d",&m);
for(int i=1;i<=m;i++)scanf("%lld",&Prime[i]);
for(int i=1;i<=n;i++)scanf("%lld",&val[i]);
for(int i=1;i<=n;i++)Change(val[i],V[i]);//转换为三进制数
for(int i=1;i<n;i++){
int a,b;
scanf("%d%d",&a,&b);
Addedge(a,b);
Addedge(b,a);
}
tot_sz=n,Root=0;
Get_Root(1,0);
dfs(Root);
printf("%d\n",ans);
}
return 0;
}