参考文献国家集训队2015论文《浅谈分块在一类在线问题的应用》-邹逍遥
题目大意
一棵n个节点的树,树的每条边长度为1或2,每次询问x,y,z。
要求输出从x开始走,每次只能走到当前节点距离$le z$的点,问最少几次能走到y
大致思路
考虑将树进行深度分块,设$size=sqrt{n}$,对于每个节点x,如果$depth[x]\%size==1$则称它是关键点。
于是这棵树就被这些关键点分成了若干块(关键点属于它下面的块),如果某一块的大小小于size,就把它和上一个块合并。
这样这棵树的每个块大小就$ge size$,块的个数就$le size$,并且每个块的直径$le size*4$,可以在$sqrt{n}$的时间求出每个询问
具体实现
对于每个节点,预处理它到上面的块中离自己最近的节点(一定是他的祖先)的距离和在z($zle size*2$)
(当$z>size$时可以一步跨过一个块)的情况下要走多少步,最后一步还剩下多长,走z后到达的节点,
以及每个节点的父亲,和到父亲节点的距离(为在块中暴力准备)
对于每个询问,如果当前两个点不在同一个块,则所在块靠下的点移动到上面的块中离自己最近的节点
如果在同一个块,则暴力让深度深的点移到它的父亲
总复杂度$O(nsqrt{n})$
#include<cstdio> #include<algorithm> #include<cmath> using namespace std; #define maxn 100005 #define maxs 350 struct Edge{ int next,to,w; }edge[maxn*2]; int n,size,fi[maxn],se,stack[maxn],top,depth[maxn],fa[maxn],block[maxn],s,remain[maxn][maxs*2],step[maxn][maxs*2],key[maxs],dis[maxn]; int reach[maxn][maxs*2],dis1[maxn]; inline void add_edge(int u,int v,int w){ edge[++se].next=fi[u],fi[u]=se,edge[se].to=v,edge[se].w=w, edge[++se].next=fi[v],fi[v]=se,edge[se].to=u,edge[se].w=w; } int dfs(int x){//第一次dfs分块,预处理出fa(父亲),dis(到父亲的距离) int si=1,k; stack[top++]=x; for(int i=fi[x];i;i=edge[i].next){ int v=edge[i].to; if(v==fa[x])continue; dis[v]=edge[i].w,fa[v]=x,depth[v]=depth[x]+1,si+=dfs(v); } if(depth[x]%size==0&&si>=size){ k=++s;block[x]=k;key[k]=x; while(stack[--top]!=x){ block[stack[top]]=k; } } return si; } void dfs1(int x){//第二次dfs预处理reach[x][z](走z距离能到达的点),dis1(到最近的不在一个块的祖先有多少距离)step(要走几步),remain(还剩多少距离) reach[x][0]=x; reach[x][1]=dis[x]==1?fa[x]:x; for(int i=2;i<=size*2;i++){ if(i-dis[x]>=1)reach[x][i]=reach[fa[x]][i-dis[x]]; else reach[x][i]=fa[x]; } if(block[x]==block[fa[x]]){ int v; for(int i=2;i<=size*2;i++){ if(block[x]==block[v=reach[x][i]]){ step[x][i]=step[v][i]+1; remain[x][i]=remain[v][i]; } else{ step[x][i]=1;remain[x][i]=remain[fa[x]][i]-dis[x]; } } dis1[x]=dis1[fa[x]]+dis[x]; } else{ for(int i=2;i<=size*2;i++){ step[x][i]=1;remain[x][i]=i-dis[x]; } dis1[x]=dis[x]; } for(int i=fi[x];i;i=edge[i].next){ int v=edge[i].to; if(v!=fa[x])dfs1(v); } } int query(int l,int r,int p){//对于p<=size*2的询问 int ans=0,sup1=0,sup2=0; while(block[l]!=block[r]){//如果两个点不在一个块 if(block[l]<block[r]){ if(dis1[l]>sup1)l=reach[l][sup1],ans+=step[l][p],sup1=remain[l][p],l=fa[key[block[l]]]; else sup1=remain[l][sup1],l=fa[key[block[l]]]; } else{ if(dis1[r]>sup2)r=reach[r][sup2],ans+=step[r][p],sup2=remain[r][p],r=fa[key[block[r]]]; else sup2=remain[r][sup2],r=fa[key[block[r]]]; } } while(l!=r){//在一个块后就暴力走 if(depth[l]>depth[r]){ if(sup1>=dis[l])sup1-=dis[l],l=fa[l]; else sup1=p-dis[l],ans++,l=fa[l]; } else { if(sup2>=dis[r])sup2-=dis[r],r=fa[r]; else sup2=p-dis[r],ans++,r=fa[r]; } } if(sup1+sup2>=p)ans--; return ans; } int query1(int l,int r,int p){//处理p>size*2的询问 int ans=0,sup1=0,sup2=0; while(block[l]!=block[r]){ if(block[l]<block[r]){ if(dis1[l]<=sup1)sup1-=dis1[l],l=fa[key[block[l]]]; else{ if(sup1>(size<<1)){ if(reach[l][size<<1]==reach[l][(size<<1)-1])sup1-=(size<<1)-1; else sup1-=(size<<1); l=reach[l][size<<1]; } else{ l=reach[l][sup1],ans++,sup1=p; } } } else{ if(dis1[r]<=sup2)sup2-=dis1[r],r=fa[key[block[r]]]; else{ if(sup2>(size<<1)){ if(reach[r][size<<1]==reach[r][(size<<1)-1])sup2-=(size<<1)-1; else sup2-=(size<<1); r=reach[r][size<<1]; } else{ r=reach[r][sup2],ans++,sup2=p; } } } } while(l!=r){ if(depth[l]>depth[r]){ if(sup1>=dis[l])sup1-=dis[l],l=fa[l]; else sup1=p-dis[l],ans++,l=fa[l]; } else { if(sup2>=dis[r])sup2-=dis[r],r=fa[r]; else sup2=p-dis[r],ans++,r=fa[r]; } } if(sup1+sup2>=p)ans--; return ans; } int main(){ int u,v,w,m; scanf("%d",&n);size=sqrt(n); for(int i=1;i<n;i++)scanf("%d%d%d",&u,&v,&w),add_edge(u,v,w); dfs(1);dfs1(1); scanf("%d",&m); for(int i=0;i<m;i++){ scanf("%d%d%d",&u,&v,&w); if(w<=size*2)printf("%d ",query(u,v,w)); else printf("%d ",query1(u,v,w)); } return 0; }