题目大意
%
给定一棵有
n
n
n 个点,边权的树,回答
m
m
m 个询问,每次询问树上距离为
k
k
k 的点对是否存在。
数据范围
n
⩽
1
0
4
,
m
⩽
100
,
边权
⩽
10000
,
k
⩽
1
0
7
n\leqslant 10^4,m\leqslant 100,\texttt{\small边权}\leqslant 10000,k\leqslant 10^7
n⩽104,m⩽100,边权⩽10000,k⩽107
题解
%
点分治模板。
考虑以
u
u
u 为根的子树,这棵子树上的路径有两种,一种是经过节点
u
u
u 的,另一种是不经过根节点的,考虑分治。对于大小为
1
1
1 的子树,无树上路径,直接返回。对于节点
u
u
u,合并不同的两个子树的路径,组成经过节点
u
u
u 的一条新路径。
对于层数为
k
k
k 的树,时间复杂度为
Θ
(
k
log
2
n
)
\Theta(k\log_2n)
Θ(klog2n),在平均情况下良好,但当树为一条链时,程序的时间复杂度将为
Θ
(
n
2
)
\Theta(n^2)
Θ(n2)。
对于一棵树
n
n
n 个节点的无根树,找到一个点,使得把树变成以该点为根的有根树时,最大子树的结点数最小,那这个点就是树的重心。
求解重心可以用DFS求出所有子树的大小,然后动态规划求解,时间复杂度为
Θ
(
n
)
\Theta(n)
Θ(n)。
如果我们选取重心作为根,然后统计从根节点出发的所有路径(
Θ
(
n
)
\Theta(n)
Θ(n)),合并从根节点出发的路径,得到所有经过根节点的路径,接着删除根节点,对剩下的每个子树分别找重心,重复以上操作,便可求出所有路径。换句话说:每个点都是根节点,但所管理的子树大小不同,而且重心的性质保证了最坏情况下划分的子树总数和
Θ
(
log
2
n
)
\Theta(\log_2 n)
Θ(log2n) 同阶。
在合并路径时,先将所有路径按长度排序先枚举询问(
Θ
(
m
)
\Theta(m)
Θ(m)),令当前询问长度为
k
k
k,再枚举每条路径(
Θ
(
n
)
\Theta(n)
Θ(n)),令路径长度为
w
w
w,最后在路径中二分查找长度为
k
−
w
k-w
k−w 的路径(
Θ
(
log
2
n
)
\Theta(\log_2n)
Θ(log2n)),注意此时两条路径不能属于同一个子树,因而合并路径的时间复杂度为
Θ
(
m
n
log
2
n
)
\Theta(mn\log_2n)
Θ(mnlog2n)。
因而程序的总时间复杂度为
T
(
n
)
=
Θ
(
m
n
log
2
2
n
)
\text{T}(n)=\Theta(mn\log_2^2 n)
T(n)=Θ(mnlog22n) 代码如下
#include<bits/stdc++.h>
#define N 10010
using namespace std;
int n,m,num,e,x,y,z,a[110],rt,size,siz[N],f[N],head[N];
bool ans[110],vis[N];
struct edge{int v,w,pre;}edges[N<<1];
struct path{
int dis,w;
bool operator<(const path &a)const{return dis<a.dis;}
} dis[N];
template<typename T>
void maxx(T& a,const T &b){a<b? a=b:0;}
void add(int x,int y,int z){
edges[++e]=(edge){y,z,head[x]};
head[x]=e;
}
void getroot(int x,int fa){//找重心 O(n)
f[x]=0;
siz[x]=1;
for(int i=head[x];i;i=edges[i].pre){
int p=edges[i].v;
if(vis[p]||p==fa) continue;
getroot(p,x);
siz[x]+=siz[p];
maxx(f[x],siz[p]);
}
maxx(f[x],size-siz[x]);
if(f[x]<f[rt]) rt=x;
}
void dfs(int x,int fa,int wh,int d){//O(n)
dis[++num]=(path){d,wh};
for(int i=head[x];i;i=edges[i].pre){
int &p=edges[i].v;
if(vis[p]||p==fa) continue;
dfs(p,x,wh,d+edges[i].w);
}
}
void work(int x){//O(nlogn)
num=0;
for(int i=head[x];i;i=edges[i].pre){//O(n)
int p=edges[i].v;
if(vis[p]) continue;
dfs(p,x,p,edges[i].w);
}
dis[++num]=(path){0,0};
sort(dis+1,dis+num+1);//O(nlogn)
for(int i=1;i<=m;++i){//O(nlogn)
if(ans[i]) continue;
int l=1;//meet
while(l<num&&dis[l].dis+dis[num].dis<a[i]) l++;
while(l<num&&!ans[i]){
if(a[i]-dis[l].dis<dis[l].dis) break;
int pot=lower_bound(dis+1,dis+1+num,(path){a[i]-dis[l].dis,0})-dis;
while(l<=num&&dis[pot].dis+dis[l].dis==a[i]&&dis[pot].w==dis[l].w) pot++;
if(dis[pot].dis+dis[l].dis==a[i]) ans[i]=1;
l++;
}
}
}
void solve(int x){//O(nlog^2n)
vis[x]=1;
work(x);
for(int i=head[x];i;i=edges[i].pre){
int p=edges[i].v;
if(vis[p]) continue;
rt=0;
siz[rt]=size=siz[p];
getroot(p,0);
solve(rt);//调用logn次
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<n;++i){
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);add(y,x,z);
} for(int i=1;i<=m;++i)
scanf("%d",a+i);
f[rt]=size=n;
getroot(1,0);
solve(rt);
for(int i=1;i<=m;++i)
puts(ans[i]?"AYE":"NAY");
}