把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【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;
}
posted @ 2020-05-29 13:07  TheLostWeak  阅读(110)  评论(0编辑  收藏  举报