1.对于简单的版本n<=500, ai<=50
直接暴力枚举两个点x,y,dfs求x与y的距离。
2.对于普通难度n<=10000,ai<=500
普通难度解法挺多
第一种,树形dp+LCA
比赛的时候,我猜测对于不为1的n个数,其中两两互质的对数不会很多,肯定达不到n^2
然后找出所有互质的对数,然后对为1的数进行特殊处理。(初略的估计了下,小于500的大概有50个质数,将n个数平均分到这些数中,最后大概有10000*50*200=10^7)
对所有的非1质数对,采用离线LCA可以搞定。
对于1的特殊情况,只需要用两次dfs,就可以找出所有1到其它点的距离和与1之间的距离和。
第二种,树形dp+容斥
这种方法从边的角度,考虑每一条边会被计算多少次,这也是树上求距离的常用方法。
由于树边都是桥边,所有只要求出边两边联通块之间互质对数。最简单的想法即是枚举每一条边,然后再分别枚举两边区域,这样的复杂度是500*500*10000 很遗憾并没有这么简单。于是用容斥原理进行优化。在枚举某条边的一边的x(1<=x<=500)的时候,考虑右边为x质因子倍数的情况,也就是容斥了。 这样可以将复杂度变为10000*500*k*2^k( k<=4)
官方题解:

附上代码:
//
// main.cpp
// 160701
//
// Created by New_Life on 16/7/1.
// Copyright © 2016年 chenhuan001. All rights reserved.
//
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <vector>
#include <algorithm>
using namespace std;
#define N 10100
vector<int> save[505];
int g[N];
struct node
{
int to,next;
}edge[2*N];
int cnt,pre[N];
int dp[N][505];
int savecnt[N][505];
int cntall[505];
long long ans;
void add_edge(int u,int v)
{
edge[cnt].to = v;
edge[cnt].next = pre[u];
pre[u] = cnt++;
}
void dfs(int s,int fa)
{
for(int p=pre[s];p!=-1;p=edge[p].next)
{
int v = edge[p].to;
if(v == fa) continue;
dfs(v,s);
for(int i=1;i<=500;i++)
{
dp[s][i] += dp[v][i];
savecnt[s][i] += savecnt[v][i];
}
}
savecnt[s][ g[s] ]++;
for(int i=0;i<(1<<save[g[s]].size());i++)
{
int tmp = 1;
for(int j=0;j<save[g[s]].size();j++)
{
if(((1<<j)&i) != 0)
{
tmp *= save[g[s]][j];
}
}
dp[s][tmp]++;
}
//int last[505];
int lastcnt[505];
for(int p=pre[s];p!=-1;p=edge[p].next)
{
int v = edge[p].to;
if(v == fa) continue;
for(int i=1;i<=500;i++)
{
//last[i] = all[i]-dp[v][i];
lastcnt[i] = cntall[i]-savecnt[v][i];
}
//对这条边进行处理
for(int i=1;i<=500;i++)
{
if(lastcnt[i] == 0) continue;
for(int j=0;j<(1<<save[i].size());j++)
{
int tcnt=0;
int tnum = 1;
for(int k=0;k<save[i].size();k++)
{
if( ((1<<k)&j)!=0 )
{
tcnt++;
tnum *= save[i][k];
}
}
if(tcnt%2 == 0) ans += lastcnt[i]*dp[v][tnum];
else ans -= lastcnt[i]*dp[v][tnum];
}
}
}
}
int main(int argc, const char * argv[]) {
for(int i=1;i<=500;i++)
{
int ti = i;
for(int j=2;j*j<=ti;j++)
{
if(ti%j == 0)
{
save[i].push_back(j);
while(ti%j==0) ti/=j;
}
}
if(ti != 1) save[i].push_back(ti);
}
//for(int i=1;i<=500;i++) printf("%d\n",save[i].size());
cnt = 0;
memset(pre,-1,sizeof(pre));
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",g+i);//得把每一项都变成最简单
int tg =1;
for(int j=0;j<save[ g[i] ].size();j++)
{
tg *= save[ g[i] ][j];
}
g[i] = tg;
}
for(int i=1;i<n;i++)
{
int a,b;
scanf("%d%d",&a,&b);
add_edge(a,b);
add_edge(b,a);
}
ans = 0;
for(int ii=1;ii<=n;ii++)
{
cntall[ g[ii] ]++;
}
dfs(1,-1);
cout<<ans<<endl;
return 0;
}
3. 对于困难难度
这就需要用到虚树这种没听过的东西了,百度学习下,然后发现原理还是很简单的。应用情景,对于一颗树,挺大,但是需要操作的结点不多,这时候把需要操作的结点重新建一颗小的树(需要用的信息不能少)。
思路概述:(抄了下)
- 枚举 因数x,x是每种质因子至多有一个的数,记录一下x有几种质因子,方便之后容斥。
- 把所有x的倍数的权值的点找出来,预处理下可以做到找出来的点的dfs序是从小到大的,预处理也可以使得每次找x的倍数的权值的点不必线性扫一遍。
- 然后对这些点 O(n) 建虚树,具体操作是相邻两个点加进去 lca,用一个栈维护下父亲链即可。[bzoj3572]是一道典型的虚树的题目。
- 构建好树后在树上 dfs 两次可以求出所有x的倍数的权值的点对之间的距离和,就是第一遍dfs记录以节点u为根的子树中,有多少个x倍数的点(可能有一些是虚树添加进来的lca点),第二遍dfs其实是枚举每条边,计算(u,v)这条边的总价值,就是它出现的次数乘以它的权值;它出现的次数就是它子树中x倍数的点的个数,乘以不在它子树中x倍数的点的个数。
- 最后容斥下就可以求出答案。
由于所有步骤均是线性的,而所有虚树加起来的总点数也是线性乘上一个常数的,所以复杂度为 O(nK),K<=128。
对于复杂度分析,我抱有不同的看法,上述过程中建虚树是O(nlog(n))的,100000以内不重复质数最多是6个,所以最大复杂度为O(64*n*log(n))
//
// main.cpp
// Xushu
//
// Created by New_Life on 16/7/1.
// Copyright © 2016年 chenhuan001. All rights reserved.
//
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <vector>
#include <algorithm>
using namespace std;
#define N 100100
#define LN 20
struct node
{
int to,next;
}edge[2*N];
int cnt,pre[N];
void add_edge(int u,int v)
{
edge[cnt].to = v;
edge[cnt].next = pre[u];
pre[u] = cnt++;
}
int deep[N];
int g[N];//记录每个点的权值
vector<int>saveall[N];//记录i所有的倍数
int sign[N];
int len[N];//每个点到根的距离
int mark[N];//标示虚树上的点是否是无用点
struct Lca_Online
{
int _n;
int dp[N][LN];
void _dfs(int s,int fa,int dd)
{
int factor[30];
int fcnt=0;
int tmp = g[s];
for(int i=2;i*i<=tmp;i++)
{
if(tmp%i == 0)
{
factor[ fcnt++ ] = i;
while(tmp%i == 0) tmp/=i;
}
}
if(tmp != 1) factor[ fcnt++ ] = tmp;
for(int i=0;i<(1<<fcnt);i++)
{
tmp = 1;
int tsign = 1;
for(int j=0;j<fcnt;j++)
if( ((1<<j)&i) != 0 )
{
tmp *= factor[j];
tsign *= -1;
}
saveall[tmp].push_back(s);
sign[tmp] = tsign;
}
deep[s] = dd;
for(int p=pre[s];p!=-1;p=edge[p].next)
{
int v = edge[p].to;
if(v == fa) continue;
_dfs(v,s,dd+1);
dp[v][0] = s;
}
}
void _init()
{
for(int j=1;(1<<j)<=_n;j++)
{
for(int i=1;i<=_n;i++)
{
if(dp[i][j-1]!=-1) dp[i][j] = dp[ dp[i][j-1] ][j-1];
}
}
}
void lca_init(int n)
{
_n = n;
memset(dp,-1,sizeof(dp));
//_dfs(firstid,-1,0);
_dfs(1,-1,0);
_init();
}
int lca_query(int a,int b)
{
if(deep[a]>deep[b]) swap(a,b);
//调整b到a的同一高度
for(int i=LN-1;deep[b]>deep[a];i--)
if(deep[b]-(1<<i) >= deep[a]) b = dp[b][i];
if(a == b) return a;
for(int i=LN-1;i>=0;i--)
{
if(dp[a][i]!=dp[b][i]) a = dp[a][i],b = dp[b][i];
}
return dp[a][0];
}
}lca;
int stk[N],top;
vector<int>tree[N];//存边
vector<int>treew[N];//存权
void tree_add(int u,int v,int w)
{
tree[u].push_back(v);
tree[v].push_back(u);
treew[u].push_back(w);
treew[v].push_back(w);
}
long long down[N];
long long ccnt[N];
long long sum[N];
int nn;
void dfs1(int s,int fa)
{
down[s] = 0;
ccnt[s] = 0;
for(int i=0;i<tree[s].size();i++)
{
int to = tree[s][i];
if(to == fa) continue;
dfs1(to,s);
down[s] += down[to] + ccnt[to]*treew[s][i];
ccnt[s] += ccnt[to];
}
if(mark[s]==1)
ccnt[s]++;
}
void dfs2(int s,int fa,long long num,long long tcnt)
{
sum[s] = down[s]+num+tcnt;
for(int i=0;i<tree[s].size();i++)
{
int to = tree[s][i];
if(to == fa) continue;
dfs2(to,s,sum[s]-down[to]-ccnt[to]*treew[s][i],(nn-ccnt[to])*treew[s][i]);
}
}
int main(int argc, const char * argv[]) {
cnt = 0;
memset(pre,-1,sizeof(pre));
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",g+i);
}
for(int i=1;i<n;i++)
{
int a,b;
scanf("%d%d",&a,&b);
add_edge(a, b);
add_edge(b, a);
}
lca.lca_init(n);
long long ans=0;
for(int x=1;x<=100000;x++)
{
if(saveall[x].size() == 0) continue;
//build virtual tree
top = 0;
stk[top++] = saveall[x][0];
tree[ saveall[x][0] ].clear();
treew[ saveall[x][0] ].clear();
mark[saveall[x][0]]=1;
for(int i=1;i<saveall[x].size();i++)
{
int v = saveall[x][i];
int plca = lca.lca_query(stk[top-1], v);//最近公共祖先
if(plca == stk[top-1]) ;//不处理
else
{
int pos=top-1;
while(pos>=0 && deep[ stk[pos] ]>deep[plca])
pos--;
pos++;
for(int j=pos;j<top-1;j++)
{
tree_add(stk[j],stk[j+1],deep[stk[j+1]]-deep[stk[j]]);
}
int prepos = stk[pos];
if(pos == 0)
{
tree[plca].clear(),treew[plca].clear(),stk[0]=plca,top=1;
mark[plca] = 0;
}
else if(stk[pos-1] != plca)
{
tree[plca].clear(),treew[plca].clear(),stk[pos]=plca,top=pos+1;
mark[plca] = 0;
}
else top = pos;
tree_add(prepos,plca,deep[prepos]-deep[plca]);
}
tree[v].clear();
treew[v].clear();
stk[top++] = v;
mark[v] = 1;
}
for(int i=0;i<top-1;i++)
{
tree_add(stk[i], stk[i+1], deep[stk[i+1]]-deep[stk[i]]);
}
//构建好了虚树,然后就是两次dfs
nn = (int)saveall[x].size();
dfs1(saveall[x][0],-1);
dfs2(saveall[x][0],-1,0,0);
long long tans=0;
for(int i=0;i<saveall[x].size();i++)
tans += sum[ saveall[x][i] ];
tans /= 2;
ans += sign[x]*tans;
}
cout<<ans<<endl;//时间,内存。
return 0;
}
来源:https://www.cnblogs.com/chenhuan001/p/5638841.html