想把这一道典型题给发出来,但是无奈一直没有找到题,直到最近才找到的。
原题链接
问题描述
树上有一些有n个节点的查询。每个查询都被描述为两个整数(X,Y)。对于每个查询,您应该找到集合E中边的最大权重,它满足以下两个条件。
1)边缘必须在从节点X到节点1的路径上
.2)边缘的权重应该小于或等于Y.
现在给你树和查询。你能找到每个查询的答案吗?
输入
输入的第一行是整数T,表示测试用例的数量。对于每种情况,第一行包含整数n表示树中的节点数。然后是n-1行,每行包含三个整数X,Y,W表示节点X和节点Y之间的边,其值为W.然后一行有一个整数Q表示查询数。在接下来的Q行中,每行包含两个整数X和Y,如上所述。
产量
对于每个测试用例,您应该输出Q行。如果没有边满足上述条件,则只输出“-1”进行此查询。否则输出此查询的答案。
输入数据
1
3
1 2 7
2 3 5
4
3 10
3 7
3 6
3 4
输出数据
7
7
5
-1
#include<bits/stdc++.h>
#define lson k<<1,l,mid
#define rson k<<1|1,mid+1,r
using namespace std;
const int MX=2e5+9;
int cnt,tot,T,n,Q,u,v,w,ans[MX];
int fa[MX],top[MX],siz[MX],son[MX],head[MX],de[MX],p[MX],t[MX<<2];
struct node{
int u,v,next,w;
}edge[MX],e[MX];
bool cmp1(node &a,node &b){
return a.w<b.w;
}
struct Node{
int x,y,id;
}q[MX];
bool cmp2(Node &a,Node &b){
return a.y<b.y;
}
void init(){
tot=cnt=0;
memset(siz,0,sizeof(siz));
memset(head,-1,sizeof(head));
memset(t,0,sizeof(t));
}
void add(int u,int v,int w){
edge[cnt].u=u;
edge[cnt].v=v;
edge[cnt].w=w;
edge[cnt].next=head[u];
head[u]=cnt++;
}
void dfs1(int u,int depth,int father){
fa[u]=father,de[u]=depth,siz[u]=1,son[u]=-1;
for( int i=head[u] ; ~i ; i=edge[i].next ){
if( edge[i].v!=father ){ // 不能往后面走
dfs1(edge[i].v,depth+1,u);
siz[u]+=siz[edge[i].v]; //siz是为了求重链的,就一工具数组
if( son[u]==-1 || siz[son[u]]<siz[edge[i].v] ) // 求u的子链中的重链,
son[u]=edge[i].v;
}
}
return ;
}
void dfs2(int u,int tp){
p[u]=++tot; //在线段树里面的标号
top[u]=tp;
if( son[u]>0 )
dfs2(son[u],tp); //所有的重链是拥有同一个top的
for( int i=head[u] ; ~i ; i=edge[i].next )
if( edge[i].v!=son[u] && edge[i].v!=fa[u] ) //不可以走重链,不可以走父链
dfs2(edge[i].v,edge[i].v); // 这里是2个edge[i].v!!!
return ;
}
void ins(int k,int l,int r,int pos,int w){
if( l==r ){
t[k]=w;
return ;
}
int mid=(l+r)>>1;
if( pos<=mid )
ins(lson,pos,w);
else
ins(rson,pos,w);
t[k]=max(t[k<<1],t[k<<1|1]);
}
int que(int k,int l,int r,int L,int R){
if( L<=l && r<=R )
return t[k];
int mid=(l+r)>>1,ans=0;
if( L<=mid )
ans=max(ans,que(lson,L,R));
if( mid<R )
ans=max(ans,que(rson,L,R));
return ans;
}
int vs(int u,int v){
int f1=top[u],f2=top[v],res=-1;
while( f1!=f2 ){
if( de[f1]>de[f2] ){ // 经过多次向上走之后,永远保证v的top(顶点)的dp(深度)是u,v中较深的
swap(f1,f2);
swap(u,v);
}
res=max(res,que(1,1,n,p[f2],p[v]));
v=fa[f2];
f2=top[v];
}
if( u==v )
return res;
if( de[v]<de[u] ) // 当走出while之后,u,v一定是在同一条重链上,可能u==v,也可能u!=v。
swap(u,v);
res=max(res,que(1,1,n,p[u],p[v]));
return res;
}
void solve(){
scanf("%d",&Q);
for( int i=1 ; i<=Q ; i++ ){
scanf("%d %d",&q[i].x,&q[i].y);
q[i].id=i;
}
for( int i=0 ; i<n-1 ; i++ )
e[i]=edge[i*2];
sort(e,e+n-1,cmp1);
sort(q+1,q+Q+1,cmp2);
int k=0;
for( int i=1 ; i<=Q ; i++ ){
while( k<=n-1 && e[k].w<=q[i].y ){ //每一次只加入小于y的节点到线段树中
int pos;
if( de[e[k].u]<de[e[k].v] )
pos=e[k].v;
else
pos=e[k].u;
ins(1,1,n,p[pos],e[k].w); //w所对应的线段树中的节点必须是dp较小的,
k++;
}
int res=vs(1,q[i].x); // 求2节点之间的距离
if( res==0 )
res=-1;
ans[q[i].id]=res;
}
for( int i=1 ; i<=Q ; i++ )
printf("%d\n",ans[i]);
return ;
}
int main()
{
freopen("input.txt","r",stdin);
scanf("%d",&T);
while( T-- ){
init();
scanf("%d",&n);
for( int i=1 ; i<=n-1 ; i++ ){
scanf("%d %d %d",&u,&v,&w);
add(u,v,w),add(v,u,w);
}
dfs1(1,1,0);
dfs2(1,1);
solve();
}
return 0;
}