HDU - 3804 Query on a tree(树链剖分+线段树+离线处理)
题目链接:点击查看
题目大意:给出一棵树,每条边上都有一个权值,给出m个查询:a,b:问从点1到点a的唯一路径上,在边权小于等于b的边中选出边权最大的值输出,若没有符合条件的边则输出-1;
题目分析:如果还不清楚树链剖分,这里有一个讲的很棒的博客:点击查看
然后看到这里就默认已经掌握了树链剖分和线段树的基础知识了。
下面分析一下这个题目的思路,题目很好理解,但如果直接暴力模拟的话必定超时,所以我们应该转换为logn级别的复杂度再下手,关于求不超过一个值的这种题,之前我做过一个不在树上的,是问玛丽奥能跳多高的一个题,用到的方法是离线处理,然后给查询按照高度排序,最后根据根据从低到高遍历,每次加入不超过此次高度的点,用线段树维护数量即可,这个题目也是类似,因为最后要求的是满足条件的最大值,所以我们可以用线段树维护区间内的最大值,然后为了让所得到的结果都能满足条件,我们可以先将m个查询储存下来,离线处理,按照权值从小到大排序,每次插入小于等于该权值的边,然后再用线段树查询区间即可,思路是有了,我们接下来的问题就是,如何将刚才的思路在树上实现呢,因为学习了dfs序,学会了可以将树上的点转化为线性的已知条件来使用,但dfs序的前提是必须是有关于祖先和子节点的问题,而这个题目的前提是必须在树上的一条链上。
所以这篇博客一开始我就让大家先学习了一下树链剖分的知识。树链剖分,顾名思义,就是将一棵树,分成一条一条的链子,这样就形成了一种线性结构,就可以利用上面的思路在树上实现了。我在大体的讲一下如何在树上用树链剖分来实现这个题目:
首先,我们先用树链剖分,找出每条重链,然后依据重链的顺序,依次给每个点进行重新编号,让每条重链上的点都挨在一起,这样的目的就是方便使用线段树进行查询。然后再用一个辅助数组记录每条重链的端点,也就是每条重链上深度最小的那个点,如果一条重链上只有一个点,那么该点的端点就是它本身。我们现在已经完成这两个操作后就可以利用线段树进行模拟了,因为我们经过树链剖分的处理后,每个节点都获得了一个全新的序号,那么我们按照新的序号进行建树,接下来的所有操作,无论是查询还是更新,都是基于新的序号来处理的,这里务必要理解!!接着按照上述思路进行,首先要对每条边进行处理,因为权值都在边上,我们不太好操作,所以我们将权值转移到点上,转移到终点上即可,比如u->v的权值为w,那么就记为点v的权值为w,然后对m个查询先储存下来并排序处理,随后遍历一遍每个查询,然后将小于等于该查询的点加入到线段树中,意思就是将权值小于等于该查询的点加入到线段树中,更新最大值,等到权值小于等于该查询的所有点都已经加入到线段树中后,对该查询进行求结果的处理,我们该怎么求结果呢?因为如果放在树上来说,起点是点1,终点是查询中的点a,那么我们需要遍历一边点1到点a的路径(路径一定是唯一的),扫一遍路径并维护最大值即可,那么我们首先要确定点a和点1的关系,即点a和点1是否在一条重链上,因为点1是整棵树的根节点,所以点1的端点就是1,我们先看一下点a的端点是什么:
- 如果点a的端点是点1的话,那么点a和点1都在一条重链上,刚才我们已经将一条重链上的点都重新编号,并让他们挨在一起了,所以我们现在只需要查询点1的新编号到点a的新编号这个区间内的最大值就是答案了。
如果点a的端点不是点1,那么说明点a和点1不在一条重链上,我们可以先沿着点a走到点a的端点上,然后在走到端点的父节点上,然后在顺着走到端点的父节点的端点上,以此类推一直往上走,直到走到点1为止,这段路径一定是唯一的
因为无论很麻烦的走,还是很简单的走,点a到点1的路径都是唯一的(因为在树上两点之间的路径一定是唯一的),又因为每条重链上的点的编号都是相互挨着的,所以当我们在每条重链上经过的时候,都可以利用线段树来查询这段路上满足条件的最大值,直到走到点1为止,这样最后的答案就一定是一路上维护而来的最大值了。
对了,这个题目中好像不需要记录深度这个属性,因为点1作为起点,它的深度一定是最小的,因为是总根节点嘛,不过写都写了,就当是练习了一下树链剖分的模板吧-.-
一定要记住!!要用新的编号来建树,用新的编号来操作!!刚学树链剖分的时候就是这里不太理解,导致看别人的例程都看不懂。。上代码了,更多的细节在注释中解释:
#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<algorithm>
#include<stack>
#include<queue>
#include<map>
#include<cmath>
#include<set>
#include<sstream>
using namespace std;typedef long long LL;const int inf=0x3f3f3f3f;const int N=1e5+100;
/***************************************************************************/
//以下是简单的线段树维护最大值,不多解释了,点更新+区间查询
//因为不习惯写类,还是都拆散开都把函数放到外面来舒服点
struct vvv
{int id,w;bool operator<(vvv a)const{return w<a.w;}
}val[N];struct Node
{int l,r,mmax;
}tree[N<<2];void build(int k,int l,int r)
{tree[k].l=l;tree[k].r=r;tree[k].mmax=0;if(l==r)return;int mid=l+r>>1;build(k<<1,l,mid);build(k<<1|1,mid+1,r);
}void pushup(int k)
{tree[k].mmax=max(tree[k<<1].mmax,tree[k<<1|1].mmax);
}void update(int k,int pos,int val)
{if(tree[k].l==tree[k].r){tree[k].mmax=val;return;}int mid=tree[k].l+tree[k].r>>1;if(mid>=pos)update(k<<1,pos,val);elseupdate(k<<1|1,pos,val);pushup(k);
}int query(int k,int l,int r)
{if(tree[k].l>r||tree[k].r<l)return 0;if(tree[k].l>=l&&tree[k].r<=r)return tree[k].mmax;return max(query(k<<1,l,r),query(k<<1|1,l,r));
}
//线段树到此为止
/***************************************************************************/
struct edge
{int u,v,w;bool operator<(edge a)const{return w<a.w;}
}edge[N];//结构体存边,用于在给每个节点重新编号后,给其赋权值vector<int>node[N];//vector存边int num[N],son[N],fa[N],deep[N];//num:子树大小,son:重儿子,fa:父节点,deep:深度void dfs1(int u,int f,int d)//求fa,num,deep,son
{num[u]=1;fa[u]=f;deep[u]=d;son[u]=0;for(int i=0;i<node[u].size();i++){int v=node[u][i];if(v==f)continue;dfs1(v,u,d+1);num[u]+=num[v];if(!son[u]||num[son[u]]<num[v]){son[u]=v;}}
}int top[N],id[N],cnt;//top:重链的端点,id:全新的编号void dfs2(int u,int root)//求top,id
{top[u]=root;id[u]=++cnt;if(son[u])dfs2(son[u],root);for(int i=0;i<node[u].size();i++){int v=node[u][i];if(v==son[u]||v==fa[u])continue;dfs2(v,v);}
} struct qu
{int id,w,ind,ans;//id:第几个查询,w:查询的权值,ind:查询的点,ans:查询的结果bool operator<(qu a)const{return w<a.w;}
}ans[N];//存查询信息int solve(int pos)//扫一遍对于查询的点到达点1的路径,并维护最大值
{int ans=0;while(top[pos]!=1)//如果不在同一条重链上{ans=max(ans,query(1,id[top[pos]],id[pos]));//维护目前重链上的最大值pos=fa[top[pos]];//走到父节点所在的重链上}ans=max(ans,query(1,id[top[pos]],id[pos]));//当点pos和点1处于同一条重链后会跳出while,所以最后需要再维护一下最大值if(ans==0)//别忘了如果不符合条件返回-1return -1;elsereturn ans;
}int main()
{
// freopen("input.txt","r",stdin);int w;cin>>w;while(w--){int n;scanf("%d",&n);for(int i=1;i<=n;i++)node[i].clear();for(int i=1;i<n;i++)//存边{int u,v,w;scanf("%d%d%d",&u,&v,&w);edge[i].u=u;edge[i].v=v;edge[i].w=w;node[u].push_back(v);node[v].push_back(u);}dfs1(1,-1,1);cnt=0;dfs2(1,1);int q;scanf("%d",&q);for(int i=1;i<=q;i++)//存查询{scanf("%d%d",&ans[i].ind,&ans[i].w);ans[i].id=i;}sort(ans+1,ans+1+q);//排序离线处理for(int i=1;i<n;i++){int u=edge[i].u;int v=edge[i].v;int w=edge[i].w;if(deep[u]>deep[v])swap(u,v);val[id[v]].id=id[v];val[id[v]].w=w;}sort(val+2,val+n+1);//不包含点1的其余n-1个点int t=2;build(1,1,n);for(int i=1;i<=q;i++){int w=ans[i].w;int id=ans[i].id;while(val[t].w<=w&&t<=n)//注意这里判断边界,一个是判断小于等于查询权值,一个是判断t不能越界{update(1,val[t].id,val[t].w);t++;}ans[id].ans=solve(ans[i].ind);} for(int i=1;i<=q;i++)printf("%d\n",ans[i].ans);}return 0;
}
HDU - 3804 Query on a tree(树链剖分+线段树+离线处理)相关推荐
- CodeForces - 609E Minimum spanning tree for each edge(最小生成树+树链剖分+线段树/树上倍增)
题目链接:点击查看 题目大意:给出一张 n 个点和 m 条边组成的无向图,现在询问包含每一条边的最小生成树 题目分析:考虑求解次小生成树的思路: 求出最小生成树 ans 枚举每一条非树边 ( u , ...
- HDU 2460 Network(双连通+树链剖分+线段树)
HDU 2460 Network 题目链接 题意:给定一个无向图,问每次增加一条边,问个图中还剩多少桥 思路:先双连通缩点,然后形成一棵树,每次增加一条边,相当于询问这两点路径上有多少条边,这个用树链 ...
- 【BZOJ-2325】道馆之战 树链剖分 + 线段树
2325: [ZJOI2011]道馆之战 Time Limit: 40 Sec Memory Limit: 256 MB Submit: 1153 Solved: 421 [Submit][Sta ...
- CodeForces - 160D Edges in MST(思维+tarjan/树链剖分+线段树)
题目链接:点击查看 题目大意:给出一张 n 个点 m 条边组成的带权无向图,现在对于每条边来说,确定一下其分类: 一定是最小生成树上的边 可能是最小生成树上的边 一定不是最小生成树的边 题目分析:两种 ...
- P2486 [SDOI2011]染色(树链剖分+线段树)
题干描述 输入描述 输出格式 对于每个询问操作,输出一行答案. 输入输出样例 输入 #1 复制 6 5 2 2 1 2 1 1 1 2 1 3 2 4 2 5 2 6 Q 3 5 C 2 1 1 Q ...
- BZOJ4127Abs——树链剖分+线段树
题目描述 给定一棵树,设计数据结构支持以下操作 1 u v d 表示将路径 (u,v) 加d 2 u v 表示询问路径 (u,v) 上点权绝对值的和 输入 第一行两个整数n和m,表示结点个数和操作数 ...
- 【BZOJ2243】[SDOI2011]染色 树链剖分+线段树
[BZOJ2243][SDOI2011]染色 Description 给定一棵有n个节点的无根树和m个操作,操作有2类: 1.将节点a到节点b路径上所有点都染成颜色c: 2.询问节点a到节点b路径上的 ...
- BZOJ2325[ZJOI2011]道馆之战——树链剖分+线段树
题目描述 口袋妖怪(又名神奇宝贝或宠物小精灵)红/蓝/绿宝石中的水系道馆需要经过三个冰地才能到达馆主的面前,冰地中 的每一个冰块都只能经过一次.当一个冰地上的所有冰块都被经过之后,到下一个冰地的楼梯才 ...
- BZOJ3862Little Devil I——树链剖分+线段树
题目大意: 给一棵树,每条边可能是黑色或白色(起始都是白色),有三种操作: 1.将u到v路径上所有边颜色翻转(黑->白,白->黑) 2.将只有一个点在u到v路径上的边颜色翻转 3.查询u到 ...
- YbtOJ-染色计划【树链剖分,线段树,tarjan】
正题 题目大意 给出nnn个点的一棵树,每个点有个颜色aia_iai,你每次可以选择一个颜色全部变成另一个颜色. 求最少多少次操作可以把一种颜色变成一个完整的连通块. 1≤k≤n≤2×1051\le ...
最新文章
- 今天看了一下攒机配置 5000元以下 参考以下
- LINQ to SQL语句(7)之Exists/In/Any/All/Contains
- 软件测试技术 homework2
- 【ES11(2020)】Promise 扩展 allSettled()
- Ubuntu14.04中安装ROS Indigo(亲测)
- C#中的异步调用剖析
- Linux中brk()系统调用,sbrk(),mmap(),malloc(),calloc()的异同【转】
- anki 新的卡片类型_用 Anki 建立高效复习错题体系
- 用反汇编理解指向指针的指针
- Oracle入门第二天(下)——单行函数
- win32汇编--创建窗口程序(RadAsm)
- Tif格式图片的读取与保存
- matlab报错Unbalanced or unexpected parenthesis or bracket.
- html在字体两边加直线,CSS文字两边添加横线的几种方法
- 天池比赛——docker初步尝试
- 在线音视频加速器终于实现了
- 家用宽带搭建个人服务器(二)
- DC学习(时序分析和命令)
- 以太网PHY寄存器分析
- 你真的知道Win 10中注销、睡眠、休眠的区别么?