P3806 点分治【模板】
给定一颗有n个点的树,询问树上距离为k的点对是否存在
点分治适合处理大规模的树上路径信息问题。
我们先随意选择一个节点作为根节点Root
所有完全位于其子树中的路径可以分为两种
一种是经过当前根节点的路径
一种是不经过当前根节点的路径
对于经过当前根节点的路径,又可以分为两种
一种是以根节点为一个端点的路径
一种是两个端点都不为根节点的路径
而后者又可以由两条属于前者链合并得到
所以,对于枚举的根节点Root,我们先计算在其子树中且经过该节点的路径对答案的贡献,再递归其子树对不经过该节点的路径进行求解
在本题中,对于经过根节点Root的路径,先枚举其所有子节点Ch,以Ch为根计算Ch子树中所有节点到Roto的距离。记节点i到当前根节点Root的距离为Dist(i)。
tf(d)表示之前处理过的子树中是否存在一个节点v使得Dist(v)=d。
若一个询问的k满足tf(k-Dist(i)) = true,则存在一条长度为k的路径。
在计算完Ch子树中所连的边能否成为答案后,我们将这些新的距离加入tf数组中。
注意在清空tf数组的时候不能直接用Memset,而应将之前占用过的Tf位置加入一个队列中,进行清空。这样才能保证时间复杂度。
#include<bits/stdc++.h> using namespace std; const int maxn=2e5+100; const int inf=2e9; int n,m; int q[maxn]; int rt; int size[maxn]; int Max[maxn]; int dist[maxn]; bool tf[maxn*100]; bool ret[maxn]; bool vis[maxn]; int tot; struct node { int u,v,w,nxt; }edge[maxn<<1]; int head[maxn]; void addedge (int u,int v,int w) { edge[tot].u=u; edge[tot].v=v; edge[tot].w=w; edge[tot].nxt=head[u]; head[u]=tot++; edge[tot].u=v; edge[tot].v=u; edge[tot].w=w; edge[tot].nxt=head[v]; head[v]=tot++; } int sum; void cal_size (int u,int pre) { //计算子树大小 //计算每个节点的最大子树大小 size[u]=1; Max[u]=0; for (int i=head[u];i!=-1;i=edge[i].nxt) { int v=edge[i].v; if (v==pre) continue; if (vis[v]) continue; cal_size(v,u); Max[u]=max(Max[u],size[v]); size[u]+=size[v]; } Max[u]=max(Max[u],sum-size[u]); if (Max[u]<Max[rt]) rt=u; } int dd[maxn]; int cnt; void cal_dist (int u,int pre) { //计算路径 dd[++cnt]=dist[u];//把每个点距离根节点的路径存到dd里 for (int i=head[u];i!=-1;i=edge[i].nxt) { int v=edge[i].v; if (v==pre) continue; if (vis[v]) continue; dist[v]=dist[u]+edge[i].w; cal_dist(v,u); } } queue<int> tag; void Divid (int u,int pre) { //分治 tf[0]=true; tag.push(0); vis[u]=true;//u已经被分治 for (int i=head[u];i!=-1;i=edge[i].nxt) { int v=edge[i].v; if (v==pre) continue; if (vis[v]) continue; dist[v]=edge[i].w;//v到根节点的路径为w cal_dist(v,u);//计算以v为根节点的子树的所有节点到u的距离 for (int k=1;k<=cnt;k++) for (int j=1;j<=m;j++) { if (q[j]>=dd[k]) { //枚举已经记录的所有路径 //如果第q次询问大于等于dd[k] //第q次询问的答案或上tf[q[j]-dd[k]] //tf数组表示长度为i的路径是否存在 ret[j]|=tf[q[j]-dd[k]]; } } for (int k=1;k<=cnt;k++) if (dd[k]<maxn*100) { tag.push(dd[k]); tf[dd[k]]=true; } cnt=0; } while (tag.size()) tf[tag.front()]=false,tag.pop(); for (int i=head[u];i!=-1;i=edge[i].nxt) { int v=edge[i].v; if (v==pre) continue; if (vis[v]) continue; sum=size[v]; rt=0; Max[rt]=inf; cal_size(v,u);//先找到以u为根节点的子树的重心 cal_size(rt,-1);//以重心为根重新计算子树大小 Divid(rt,u);//对u这颗子树进行分治 } } int main () { scanf("%d%d",&n,&m); for (int i=0;i<=n;i++) head[i]=-1; for (int i=1;i<n;i++) { int u,v,w; scanf("%d%d%d",&u,&v,&w); addedge(u,v,w); } for (int i=1;i<=m;i++) scanf("%d",q+i); rt=0; Max[rt]=inf; sum=n; cal_size(1,-1); cal_size(rt,-1); Divid(rt,-1); for (int i=1;i<=m;i++) { if (ret[i]) printf("AYE\n"); else printf("NAY\n"); } }