【BZOJ2117】[2010国家集训队] Crash的旅游计划(动态点分治)
大致题意: 给定一棵无根树,求从每个点出发第\(k\)小的距离。
前言
感觉这才是真正的点分树模板题,比【BZOJ3730】震波要简单良心许多。
本来已经做好了码上百行,肝一两个小时的准备,结果不知不觉就写完了。。。
写完有几个小锅,也是简单调了调就揪出来了,总共用时也就半小时左右。
大致思路
一个很朴素的三只\(log\)的做法。
对于每次询问,先二分答案\(D\),然后就变成了询问到该点距离小于等于\(D\)的点数。
则考虑对于每个点我们开一个\(vector\)存下子树内所有点到它的距离,并对每个子节点开一个\(vector\)存下子树内所有点到它父节点的距离。
我们在点分树上暴力往上跳,设与当前父亲距离为\(d\),则通过\(upper\_bound\)求出当前父亲子树内到其距离小于等于\(D-d\)的点数,然后以类似的方式求出上一个父亲子树内到当前父亲距离小于等于\(D-d\)的点数并从答案中减去(不然不仅会重复计算,而且还会出错)。
应该可以说是比较套路的吧。
二分一只\(log\),暴跳一只\(log\),\(upper\_bound\)一只\(log\),实际跑起来也不会很慢(毕竟这可是单点时限\(15s\)的题目啊)。
代码
#pragma GCC optimize(2)
#pragma GCC optimize("inline")
#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 LN 20
#define add(x,y,v) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].val=v)
#define Gmax(x,y) (x<(y)&&(x=(y)))
#define pb push_back
using namespace std;
int n,k,ee,lnk[N+5];struct edge {int to,nxt,val;}e[N<<1];
class FastIO
{
private:
#define FS 100000
#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
#define pc(c) (C==E&&(clear(),0),*C++=c)
#define D isdigit(c=tc())
int T;char c,*A,*B,*C,*E,FI[FS],FO[FS],S[FS];
public:
I FastIO() {A=B=FI,C=FO,E=FO+FS;}
Tp I void read(Ty& x) {x=0;W(!D);W(x=(x<<3)+(x<<1)+(c&15),D);}
Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
Tp I void writeln(Ty x) {W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);pc('\n');}
I void clear() {fwrite(FO,1,C-FO,stdout),C=FO;}
#undef D
}F;
namespace DynamicDotSolver//动态点分治
{
int rt,Sz[N+5],Mx[N+5],used[N+5],t[N+5],f[N+5][LN+5],d[N+5][LN+5];
int T,Sx[N+5],Sd[N+5];vector<int> v[N+5],g[N+5];
I void GetRt(RI x,RI s,CI lst=0)//找重心
{
Sz[x]=1,Mx[x]=0;for(RI i=lnk[x];i;i=e[i].nxt) !used[e[i].to]&&
e[i].to^lst&&(GetRt(e[i].to,s,x),Sz[x]+=Sz[e[i].to],Gmax(Mx[x],Sz[e[i].to]));
Gmax(Mx[x],s-Sz[x]),Mx[x]<Mx[rt]&&(rt=x);
}
I void dfs(CI x,CI d=0,CI lst=0)//遍历子树
{
Sx[++T]=x,Sd[T]=d;for(RI i=lnk[x];i;i=e[i].nxt)//开栈存下每个点及其距离
!used[e[i].to]&&e[i].to^lst&&(dfs(e[i].to,d+e[i].val,x),0);
}
I void Build(RI x)//建点分树
{
RI i,y;for(used[x]=1,i=lnk[x];i;i=e[i].nxt) if(!used[e[i].to])
{
rt=0,GetRt(e[i].to,Sz[e[i].to]),dfs(e[i].to,e[i].val);
W(T) y=Sx[T],f[y][++t[y]]=x,v[x].pb(d[y][t[y]]=Sd[T]),g[rt].pb(Sd[T--]);Build(rt);//处理栈中信息
}
}
I void Init()//初始化
{
Mx[rt=0]=n,GetRt(1,n),Build(rt);for(RI i=1;i<=n;++i)
f[i][0]=i,reverse(f[i]+1,f[i]+t[i]+1),reverse(d[i]+1,d[i]+t[i]+1),//翻转父亲数组及其距离数组
sort(v[i].begin(),v[i].end()),sort(g[i].begin(),g[i].end());//给vector排序以便upper_bound
}
I bool Check(CI x,CI D)//验证二分的答案
{
#define GV(V,D) (upper_bound(V.begin(),V.end(),D)-V.begin())
RI p=GV(v[x],D);for(RI i=1;i<=t[x];++i) D>=d[x][i]&&//暴力上跳,当距离≤D时计算答案(注意不能break)
(p+=GV(v[f[x][i]],D-d[x][i])-GV(g[f[x][i-1]],D-d[x][i])+1);//减去上个父亲的贡献
return p>=k;
}
}using namespace DynamicDotSolver;
int main()
{
RI i,x,y,z;for(F.read(n,k),i=1;i^n;++i) F.read(x,y,z),add(x,y,z),add(y,x,z);
RI l,r,mid;for(Init(),i=1;i<=n;++i)
{l=1,r=1e9;W(l<r) Check(i,mid=l+r-1>>1)?r=mid:l=mid+1;F.writeln(r);}//二分答案
return F.clear(),0;
}
待到再迷茫时回头望,所有脚印会发出光芒