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

【BZOJ4009】[HNOI2015] 接水果(二维数点)

点此看题面

大致题意: 给定一棵无根树,选定若干条树上路径(盘子)并分别给出权值,每次询问一条树上路径(水果)包含的路径(盘子)中第\(k\)小的权值。

前言

这题还是比较水的,一眼就看出来是二维数点,不过想一个比较容易写的方法想了挺久(一开始还想着写树套树)。

所以为什么是水果的路径包含盘子的路径才能被接到。。。

二维数点

考虑树上路径包含,可以转化为\(dfs\)序的大小关系。

设有一个盘子\((u,v)\)(设\(dI_u<dI_v\)),它能接到的水果\((x,y)\)(设\(dI_x<dI_y\))必然要满足某种条件,而这需要分两类讨论:

  • \(u\)\(v\)不互为祖先关系,则\(x\)应在\(u\)的子树内,\(y\)应在\(v\)的子树内,即\(dI_u\le dI_x\le dO_u,dI_v\le dI_y\le dO_v\)
  • \(u\)\(v\)的祖先,则设\(w\)为子树中包含\(v\)的那个\(u\)的子节点,那么只要\(x,y\)只要有一个在\(w\)子树外,一个在\(v\)子树内即可,因此有\(1\le dI_x< dI_w,dI_v\le dI_y\le dO_v\)\(dI_v\le dI_x\le dO_v,dO_w<y\le n\)

这东西如果考虑树套树,因为问的是第\(k\)大,那么就需要主席树套主席树,想想都非常可怕。

但是,众所周知,树套树的题目,强制在线的可以分块爆踩(hl666曰),不强制在线的可以CDQ分治、整体二分乱搞。

因此,对于这题我们考虑整体二分,二分当前答案,则只要知道有多少个小于等于当前答案的点即可。

考虑每个盘子贡献的是一个矩形(或两个),那么就可以用扫描线来做。

这个做法就非常容易实现了。

代码

#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 40000
#define LN 20
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
#define swap(x,y) (x^=y^=x^=y)
using namespace std;
int n,P,Q,dc,dv[N+5],ee,lnk[N+5];struct edge {int to,nxt;}e[N<<1];struct Pl{int x,y,v;}p[N+5];
struct Qry
{
	int id,x,y,k;I bool operator < (Con Qry& o) Con {return x<o.x;}
}q[N+5];
struct Seg
{
	int x,l,r,v;I Seg(CI p=0,CI a=0,CI b=0,CI t=0):x(p),l(a),r(b),v(t){}
	I bool operator < (Con Seg& o) Con {return x<o.x;}
}s[4*N+5];int cnt;
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;}
}F;
struct Tree
{
	int d,dI[N+5],dO[N+5],dep[N+5],f[N+5][LN+5];
	I void dfs(CI x=1)//遍历
	{
		RI i;for(dI[x]=++d,i=1;i<=LN;++i) f[x][i]=f[f[x][i-1]][i-1];//预处理倍增数组
		for(i=lnk[x];i;i=e[i].nxt) !dI[e[i].to]&&
			(dep[e[i].to]=dep[f[e[i].to][0]=x]+1,dfs(e[i].to),0);dO[x]=d;
	}
	I bool IsFa(CI x,CI y) {return dI[x]<=dI[y]&&dI[y]<=dO[x];}//判断是不是祖先(其实就是判在不在子树内)
	I int Jump(CI x,RI y)//倍增跳儿子
	{
		for(RI i=0,d=dep[x]+1;d^dep[y];++i) (d^dep[y])>>i&1&&(y=f[y][i]);return y;
	}
}T;
struct BIT//树状数组
{
	int a[N+5];I void U(RI x,CI v) {W(x<=n+1) a[x]+=v,x+=x&-x;}
	I int Q(RI x,RI t=0) {W(x) t+=a[x],x-=x&-x;return t;}
}B;
class WholeSolver//整体二分
{
	private:
		Seg tl[N+5],tr[N+5];Qry kl[N+5],kr[N+5];
	public:
		int ans[N+5];
		I void Solve(CI l,CI r,CI sl,CI sr,CI ql,CI qr)//整体二分
		{
			RI i,j,ul=0,ur=0,vl=0,vr=0;
			if(l==r||ql>qr) {for(i=ql;i<=qr;++i) ans[q[i].id]=l;return;}//边界
			RI mid=l+r>>1;for(i=sl,j=ql;j<=qr;++j)
			{
				W(i<=sr&&s[i].x<=q[j].x) abs(s[i].v)>mid?tr[++ur]=s[i++]//如果值大于当前二分的答案
					:(B.U(s[i].l,s[i].v>0?1:-1),B.U(s[i].r+1,s[i].v>0?-1:1),tl[++ul]=s[i++]);//树状数组上修改
				B.Q(q[j].y)>=q[j].k?(kl[++vl]=q[j],0):(kr[++vr]=q[j]).k-=B.Q(q[j].y);//判断询问扔哪边
			}
			W(i<=sr) abs(s[i].v)>mid?tr[++ur]=s[i++]//对剩余修改分组,完成所有修改也就完成了清空
				:(B.U(s[i].l,s[i].v>0?1:-1),B.U(s[i].r+1,s[i].v>0?-1:1),tl[++ul]=s[i++]);
			for(i=1;i<=ul;++i) s[sl+i-1]=tl[i];for(i=1;i<=ur;++i) s[sl+ul+i-1]=tr[i];
			for(i=1;i<=vl;++i) q[ql+i-1]=kl[i];for(i=1;i<=vr;++i) q[ql+vl+i-1]=kr[i];
			Solve(l,mid,sl,sl+ul-1,ql,ql+vl-1),Solve(mid+1,r,sl+ul,sl+ul+ur-1,ql+vl,ql+vl+vr-1);//递归
		}
}S;
int main()
{
	RI i,x,y;for(F.read(n,P,Q),i=1;i^n;++i) F.read(x,y),add(x,y),add(y,x);T.dfs();
	for(i=1;i<=P;++i) F.read(p[i].x,p[i].y,p[i].v),dv[i]=p[i].v;sort(dv+1,dv+P+1);
	for(dc=unique(dv+1,dv+P+1)-dv-1,i=1;i<=P;++i)
		p[i].v=lower_bound(dv+1,dv+dc+1,p[i].v)-dv,T.dI[p[i].x]>T.dI[p[i].y]&&swap(p[i].x,p[i].y),//离散化
		#define Add(a,b,c,d,v) (c<=d&&(s[++cnt]=Seg(a,c,d,v),s[++cnt]=Seg(b+1,c,d,-v),0))//加一个矩形
		!T.IsFa(p[i].x,p[i].y)?Add(T.dI[p[i].x],T.dO[p[i].x],T.dI[p[i].y],T.dO[p[i].y],p[i].v)//不互为祖先关系
		:(x=T.Jump(p[i].x,p[i].y),Add(1,T.dI[x]-1,T.dI[p[i].y],T.dO[p[i].y],p[i].v),//互为祖先关系
		Add(T.dI[p[i].y],T.dO[p[i].y],T.dO[x]+1,n,p[i].v));sort(s+1,s+cnt+1);//最后记得排序
	for(i=1;i<=Q;++i) F.read(q[i].x,q[i].y,q[i].k),q[i].id=i,//读入询问
		(q[i].x=T.dI[q[i].x])>(q[i].y=T.dI[q[i].y])&&swap(q[i].x,q[i].y);sort(q+1,q+Q+1);//记得排序
	for(S.Solve(1,dc,1,cnt,1,Q),i=1;i<=Q;++i) F.writeln(dv[S.ans[i]]);return F.clear(),0;//整体二分后输出答案
}
posted @ 2020-06-03 12:43  TheLostWeak  阅读(121)  评论(0编辑  收藏  举报