【洛谷6626】[省选联考 2020 B 卷] 消息传递(点分治基础题)
- 给定一棵\(n\)个点的树,\(m\)次询问到点\(x\)距离为\(d\)的点数。
- 数据组数\(\le5,n,m\le10^5\)
点分治基础题
应该算是一个比较裸的点分治题目了吧。
我们把询问离线扔到对应的节点上,然后无非就是考虑跨越当前分治重心的所有路径对于当前子树内所有询问的贡献。
那么对于某个子树内一个节点的询问,我们在\(dfs\)完这棵子树之后先给这个询问减去从这个点到分治重心再走回子树内距离为\(d\)的点数,即子树内到根节点距离为\(d-dep_x\)的点数(\(x\)为询问点),再把这个子树内的距离计数数组统计到总的计数数组中。
\(dfs\)完所有子树一遍后,再次\(dfs\)所有子树计算一次答案,这时候就可以直接加上到根节点距离为\(d-dep_x\)的点数了,因为同子树中的非法方案数已经在前面被我们减去了。
由于一次点分治需要枚举子树中的所有询问,那么“良心”出题人就有可能把一堆询问塞在叶节点上,因此一个点的大小应该是这个点的询问个数+\(1\)(点数)。
代码:\(O(nlogn)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
using namespace std;
int n,m,ee,lnk[N+5];struct edge {int to,nxt;}e[2*N+5];
struct Q {int p,d;I Q(CI x=0,CI y=0):p(x),d(y){}};vector<Q> q[N+5];
namespace FastIO
{
#define FS 100000
#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
#define pc(c) (FC==FE&&(clear(),0),*FC++=c)
int OT;char oc,FI[FS],FO[FS],OS[FS],*FA=FI,*FB=FI,*FC=FO,*FE=FO+FS;
I void clear() {fwrite(FO,1,FC-FO,stdout),FC=FO;}
Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
Tp I void writeln(Ty x) {W(OS[++OT]=x%10+48,x/=10);W(OT) pc(OS[OT--]);pc('\n');}
}using namespace FastIO;
int rt,Sz[N+5],Mx[N+5],ud[N+5];I void GetRt(CI x,RI s,CI lst=0)//找重心
{
Sz[x]=1+q[x].size(),Mx[x]=0;for(RI i=lnk[x];i;i=e[i].nxt) e[i].to^lst&&
!ud[e[i].to]&&(GetRt(e[i].to,s,x),Sz[x]+=Sz[e[i].to],Mx[x]=max(Mx[x],Sz[e[i].to]));
(Mx[x]=max(Mx[x],s-Sz[x]))<Mx[rt]&&(rt=x);
}
int cnt[N+5],dis[N+5];I void dfs(CI x,CI d=1,CI lst=0)//遍历子树,统计距离数组
{
++cnt[dis[x]=d];for(RI i=lnk[x];i;i=e[i].nxt) e[i].to^lst&&!ud[e[i].to]&&(dfs(e[i].to,d+1,x),0);
}
int ans[N+5];I void Calc(CI x,int* s,CI op,CI lst=0)//更新询问贡献
{
for(RI i=0,sz=q[x].size();i^sz;++i) q[x][i].d>=dis[x]&&(ans[q[x][i].p]+=op*s[q[x][i].d-dis[x]]);//枚举所有询问
for(RI i=lnk[x];i;i=e[i].nxt) e[i].to^lst&&!ud[e[i].to]&&(Calc(e[i].to,s,op,x),0);
}
int tot[N+5];I void Solve(RI x)//点分治
{
RI i,j,sz;for(ud[x]=tot[0]=1,i=lnk[x];i;i=e[i].nxt) if(!ud[e[i].to])
for(dfs(e[i].to),Calc(e[i].to,cnt,-1),j=1;cnt[j];++j) tot[j]+=cnt[j],cnt[j]=0;//先减去子树内非法贡献,再加到总数组中
for(i=lnk[x];i;i=e[i].nxt) !ud[e[i].to]&&(Calc(e[i].to,tot,1),0);//更新所有子树询问
for(i=0,sz=q[x].size();i^sz;++i) ans[q[x][i].p]+=tot[q[x][i].d];for(i=0;tot[i];++i) tot[i]=0;//更新当前子树询问,然后清空数组
for(i=lnk[x];i;i=e[i].nxt) !ud[e[i].to]&&(rt=0,GetRt(e[i].to,Sz[e[i].to]),Solve(rt),0);//递归
}
int main()
{
RI Tt,i,x,y;read(Tt),Mx[0]=1e9;W(Tt--)
{
for(read(n,m),ee=0,i=1;i<=n;++i) lnk[i]=ud[i]=0,q[i].clear();//清空
for(i=1;i^n;++i) read(x,y),add(x,y),add(y,x);
for(i=1;i<=m;++i) read(x,y),q[x].push_back(Q(i,y)),ans[i]=0;//把询问扔到对应节点上
for(rt=0,GetRt(1,n+m),Solve(rt),i=1;i<=m;++i) writeln(ans[i]);
}return clear(),0;
}
待到再迷茫时回头望,所有脚印会发出光芒