题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3804
题目大意:
给你一棵树,树上每个边有一个权值,然后给你一些查询操作, 输入p,q,查询结点p到结点q之间的边中权值不超过q的最大值。
解题思路:
这几天学习了树链剖分,从一脸蒙蔽到开始手打模板,感觉终于理解的还算行了,起码基础的东西都知道了,接下来就是多刷一些题熟练一下了。
这道题也是最近做的唯一不是裸树链剖分的题目(不过也差不多),刚开始写了个裸的,正常维护了一颗线段树,就是查询的时候加了个小判断条件,结果理所当然的 t 掉了。然后没办法感觉有点没法解决就去discuss里面看了一下,好吧,竟然要用离线,还是对离线不够敏感,也是这一类的题目做的比较少,以后要专门的练习一下。
离线的方法呢就是将边的权值从小到大排序,再将查询(p,q)按q的大小从小到大排序。这里进行每一次查询操作之前将权值比 q 小的边加入线段树更新就行了,剩下就是树链剖分和线段树的模板了,以下贴代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cstdlib>
#include <cmath>
#include <vector>
#include <queue>
#include <map>
#include <algorithm>
#include <set>
#include <functional>
#define pb push_back
#define lson rt<<1
#define rson rt<<1|1
using namespace std;
typedef long long ll;
const int N=100010;
const int INF=1e9+7;
int cnt,n,m,pos,sp,sq,step,ans;
int dep[N],size[N],son[N],top[N],w[N],fa[N],res[N];
int head[N];
struct node //链表建边
{
int u,v,w;
int next;
bool operator<(const node s) const
{
return w<s.w;
}
}edge[N*2],e[N];
struct que //询问
{
int st,sk,id;
bool operator<(const que q) const
{
return sk<q.sk;
}
}qu[N];
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++;
}
struct tree
{
int l,r,mid;
int ma;
}t[N<<2];
void dfs1(int u,int fat,int de) //树链剖分模板在此不详解了
{
dep[u]=de;fa[u]=fat;size[u]=1;son[u]=0;
for(int i=head[u];i!=-1;i=edge[i].next)
{
int v=edge[i].v;
if(v==fa[u])
continue;
dfs1(v,u,de+1);
size[u]+=size[v];
if(son[v]==-1||(size[son[u]]<size[v]))
son[u]=v;
}
return ;
}
void dfs2(int u,int tp)
{
top[u]=tp;
w[u]=++step;
if(son[u]>0)
dfs2(son[u],tp);
for(int i=head[u];i!=-1;i=edge[i].next)
{
int v=edge[i].v;
if(v==fa[u]||v==son[u])
continue;
dfs2(v,v);
}
return ;
}
void build(int l,int r,int rt)
{
int m=(l+r)>>1;
t[rt].l=l;t[rt].r=r;
t[rt].mid=m;
t[rt].ma=-INF;
if(l==r)
return ;
build(l,m,lson);
build(m+1,r,rson);
}
void pushup(int rt) //正常的pushup
{
t[rt].ma=max(t[lson].ma,t[rson].ma);
}
void query(int p,int q,int rt) //正常的线段树查询
{
if(t[rt].l>=p&&t[rt].r<=q)
{
ans=max(t[rt].ma,ans);
return ;
}
if(p<=t[rt].mid)
query(p,q,lson);
if(q>t[rt].mid)
query(p,q,rson);
pushup(rt);
}
void update(int pos,int flag,int rt) //正常的线段树更新
{
if(t[rt].l==t[rt].r)
{
t[rt].ma=flag;
return ;
}
if(pos<=t[rt].mid)
update(pos,flag,lson);
if(pos>t[rt].mid)
update(pos,flag,rson);
pushup(rt);
}
void vs(int u,int v) //查询u到v之间的合法区间
{
int f1=top[u],f2=top[v];
ans=-INF;
while(f1!=f2)
{
if(dep[f2]>dep[f1])
{
swap(f1,f2);
swap(u,v);
}
query(w[f1],w[u],1);
u=fa[f1];
f1=top[u];
}
if(u==v)
return ;
if(dep[u]>dep[v])
swap(u,v);
query(w[son[u]],w[v],1);
}
void slove()
{
build(0,step,1); //建立线段树
for(int i=0;i<n-1;i++) //为了方便先将边储存起来
e[i]=edge[i*2];
e[n-1].w=INF; //这里赋为INF下面方便
sort(e,e+n-1);
scanf("%d",&m);
for(int i=1;i<=m;i++) //储存询问
{
scanf("%d%d",&qu[i].st,&qu[i].sk);
qu[i].id=i;
}
sort(qu+1,qu+1+m);
int k=0;
for(int i=1;i<=m;i++)
{
while(e[k].w<=qu[i].sk) //在查询前将权值比qu[i].sk小的边加入线段树
{
if(dep[e[k].u]>dep[e[k].v])
swap(e[k].u,e[k].v);
update(w[e[k].v],e[k].w,1);
k++;
}
vs(qu[i].st,1);
if(ans==-INF)
res[qu[i].id]=-1;
else
res[qu[i].id]=ans;
}
for(int i=1;i<=m;i++)
printf("%d\n",res[i]);
}
int main()
{
int QAQ;
scanf("%d",&QAQ);
while(QAQ--)
{
cnt=0;step=0;
memset(head,-1,sizeof head);
int u,v,w;
scanf("%d",&n);
for(int i=1;i<n;i++)
{
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);
add(v,u,w);
}
dfs1(1,0,1);
dfs2(1,1);
slove();
}
}