#4509. Three points 2

题目描述

有一棵大树.

已知 a , b , c a,b,c a,b,c,你需要找出 X , Y , Z X,Y,Z X,Y,Z,满足 d i s ( X , Y ) = a ,   d i s ( X , Z ) = b ,   d i s ( Y , Z ) = c dis(X,Y)=a,\ dis(X,Z)=b,\ dis(Y,Z)=c dis(X,Y)=a, dis(X,Z)=b, dis(Y,Z)=c.

数据范围

3 ≤ n , Q ≤ 2 × 1 0 5 3\le n,Q\le 2\times 10^5 3n,Q2×105 1 ≤ a , b , c &lt; n 1\le a,b,c&lt;n 1a,b,c<n

题解

考虑一下到这三条路径会交于一个点 A A A ,而 A A A X , Y , Z X,Y,Z X,Y,Z的路径长度可以先算出来,设为 x , y , z x,y,z x,y,z x ≥ y ≥ z x \ge y \ge z xyz

我们发现如果一个点能够成为这个点 A A A,则需要满足它的三长链分别不小于 x , y , z x,y,z x,y,z ,即如果三长链长度为 x ′ ≥ y ′ ≥ z ′ x&#x27; \ge y&#x27; \ge z&#x27; xyz ,则 x ≤ x ′ , y ≤ y ′ , z ≤ z ′ x \le x&#x27;,y \le y&#x27;,z \le z&#x27; xx,yy,zz

所以我们可以利用 d p dp dp 换根把每个点的三长链及其对应的叶子结点求出来,然后就是个经典的三维偏序问题啦,可以直接用树状数组解决

效率: O ( n l o g n ) O(nlogn) O(nlogn)

代码

#include <bits/stdc++.h>
#define P pair<int,int>
#define _(d) while(d(isdigit(c=getchar())))
using namespace std;const int N=2e5+5,Z=N<<1;
int R(){char c;_(!);int x=c^48;_()x=(x<<3)+(x<<1)+(c^48);return x;}
int a[N],b[N],c[N],s[N],Q,n,hd[N],V[Z],nx[Z],t,fa[N][20],dp[N],f[N][3],g[N][3],d[N][3],w[4],k[4],e[N],F[Z][22],Lg[Z],in[N],C;
P ax[N];struct O{int a,b,c,x,g;}p[Z],h[N];
bool cmp(O A,O B){
	if (A.a==B.a){
		if (A.b==B.b){
			if (A.c==B.c) return A.g>B.g;
			return A.c>B.c;
		}
		return A.b>B.b;
	}
	return A.a>B.a;
}
void add(int u,int v){
	nx[++t]=hd[u];V[hd[u]=t]=v;in[v]++;
}
void dfs(int x,int fr){
	fa[x][0]=fr;dp[x]=dp[fr]+1;
	F[++C][0]=x;e[x]=C;
	for (int i=1;fa[fa[x][i-1]][i-1];i++)
		fa[x][i]=fa[fa[x][i-1]][i-1];
	for (int i=hd[x];i;i=nx[i])
		if (V[i]!=fr) dfs(V[i],x),F[++C][0]=x;
}
void work(int x,int j,int v){
	for (int i=0;i<3;i++)
		if (f[x][j]+1>f[v][i]){
			for (int s=2;s>i;s--)
				f[v][s]=f[v][s-1],d[v][s]=d[v][s-1],g[v][s]=g[v][s-1];
			f[v][i]=f[x][j]+1;d[v][i]=d[x][j];g[v][i]=x;break;
		}
}
void dp1(int x,int fr){
	f[x][0]=0;f[x][1]=f[x][2]=-1;d[x][0]=x;
	for (int v,i=hd[x];i;i=nx[i])
		if ((v=V[i])!=fr)
			dp1(v,x),work(v,0,x);
}
void dp2(int x,int fr){
	if (in[x]>1) p[++t]=(O){f[x][0],f[x][1],f[x][2],x,1};
	for (int v,i=hd[x];i;i=nx[i])
		if ((v=V[i])!=fr) work(x,g[x][0]==v,v),dp2(v,x);
}
void update(int x,P v){
	for (;x;x-=x&-x) ax[x]=max(ax[x],v);
}
P query(int x){
	P A=ax[x];
	for (;x<=n;x+=x&-x) A=max(A,ax[x]);
	return A;
}
int Min(int x,int y){return dp[x]<dp[y]?x:y;}
int lca(int l,int r){
	if (l>r) swap(l,r);int i=Lg[r-l+1];
	return Min(F[l][i],F[r-(1<<i)+1][i]);
}
int dis(int u,int v){
	int l=lca(e[u],e[v]);
	return dp[u]+dp[v]-(dp[l]<<1);
}
int find(int u,int y,int ds){
	int v=d[u][y],l=lca(e[u],e[v]);
	if (dp[u]-dp[l]<ds){
		ds-=dp[u]-dp[l];
		ds=dp[v]-dp[l]-ds;u=v;
	}
	for (int i=0;ds;ds>>=1,i++)
		if (ds&1) u=fa[u][i];
	return u;
}
int main(){
	n=R();for (int u,v,i=1;i<n;i++)
		u=R()+1,v=R()+1,add(u,v),add(v,u);
	dfs(1,0);t=0;dp1(1,0);dp2(1,0);Q=R();
	for (int i=2;i<=C;i++) Lg[i]=Lg[i>>1]+1;
	for (int i=C;i;i--)
		for (int j=1;i+(1<<j)<=C+1;j++)
			F[i][j]=Min(F[i][j-1],F[i+(1<<(j-1))][j-1]);
	for (int x,y,z,i=1;i<=Q;i++){
		a[i]=R();b[i]=R();c[i]=R();x=a[i]+b[i]-c[i];
		y=a[i]+c[i]-b[i];z=b[i]+c[i]-a[i];
		if (x>=0 && y>=0 && z>=0 && !((x&1)&(y&1)&(z&1))){
			x>>=1;y>>=1;z>>=1;if (x<y) swap(x,y);
			if (x<z) swap(x,z);if (y<z) swap(y,z);
			h[i]=p[++t]=(O){x,y,z,i,0};
		}
	}
	sort(p+1,p+t+1,cmp);
	for (int i=1;i<=t;i++)
		if (p[i].g) update(p[i].b,make_pair(p[i].c,p[i].x));
		else{
			P A=query(p[i].b);
			if (A.second && A.first>=p[i].c) s[p[i].x]=A.second;
		}
	for (int i=1;i<=Q;i++)
		if (s[i]){
			w[k[1]=1]=find(s[i],0,h[i].a);
			w[k[2]=2]=find(s[i],1,h[i].b);
			w[k[3]=3]=find(s[i],2,h[i].c);
			do{
				if (dis(w[k[1]],w[k[2]])==a[i] && dis(w[k[1]],w[k[3]])==b[i] && dis(w[k[2]],w[k[3]])==c[i]){
					printf("%d %d %d\n",w[k[1]]-1,w[k[2]]-1,w[k[3]]-1);break;
				}
			}while(next_permutation(k+1,k+4));
		}
		else puts("-1");
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值