NOIP2016 天天爱跑步 线段树合并
题意:
分析:
记得当初做这道题的时候,又差分又倍增还开桶,十分毒瘤,后来学到了线段树合并,又看到了这道题,所以就写了这篇解题报告。
每一条从S到T的路径(其实就是链),可以拆分为S -> lca -> T,一段深度减少,一段深度增加(或者只有一段,不过性质是一样的)所以我们想一想可以明白,对于每个点,我们可以向其子树中存在的起点和终点统计这个点的观察员会不会碰上这条线路的人。
而我们怎么统计呢?
我们是否有一种办法,可以把一个点的询问放在线段树上处理,然后就可以理所应当的用线段树合并的手段,来自下而上处理每个点的询问。
我们考虑,当我们当前要处理的点在某条路线的S -> lca这一段,那么当前位置设为x,对于所有的S,假如depth[S]=w[x](即深度等于这个观察员记录的时间)这样的S可以贡献x点的答案。
当我们要处理的点在某条路线的lca -> T这一段上,我们需要和刚才那种区别处理,为了不重复统计,或许可以开不同的线段树,但是未免太不优秀了,所以我们用这样的手法。
首先一个背景是,我们对于每一个点开两个vector,一个代表插入标记,一个代表待删除标记。对于一条路线,我们于起点处打一个插入标记,值为depth[S],在终点处打一个插入标记,值为2*depth[lca(S,T)] - depth[S],引用某位大神的思路,这个相当于把起点沿着lca翻上去的这么一个位置。然后这两个标记,当我们统计到lca以上的时候,需要消除这两个位置的贡献,那我们就需要在lca(S,T)和fa[lca(S,T)]两个点打上相应的删除标记来消除贡献,(其实只要递归的时候在相应节点的线段树上,用单点修改把标记一口气都打上,之后我们只要在回溯的时候合并线段树就可以让每个标记按照题意贡献答案,这也是这种思路的巧妙之处)
这样分有什么好处?
首先好统计,假如在x点,找到的在depth[x] + w[x]处如果有标记,有几个标记就代表有几个起点出发的玩家会被这个观察员看到,然后如果找到depth[x] - w[x]处如果有标记,有几个标记就代表有几个从lca到终点的玩家会被这个节点的观察员看到。
其次是贡献不会互相影响,depth[x] + w[x]一定比x点深,于是不可能是被翻上去的那个(大白话很好理解),所以是起点的标记,同理呢,depth[x] - w[x]一定是被翻上去的那个。
而且,已经统计过的整条链会在lca处被删除所有贡献,所以答案正确性毫无问题,复杂度是O(NlogN)的,也说的过去。
(但是实测,下面这份代码稍微差点的评测机就会TLE,我也不知道为什么)
代码:
1 #include<bits/stdc++.h> 2 #define inf 600000 3 #define lc(x) t[x].l 4 #define rc(x) t[x].r 5 using namespace std; 6 const int N=300005,M=20000005; 7 struct segt{int l,r,sm;}t[M]; 8 struct node{int y,nxt;}e[N*3]; 9 vector<int>p1[N],p2[N];int h[N],c=1; 10 int n,m,ans[N],q[N],rt[N],d[N],fa[N][20],cnt; 11 void add(int x,int y){ 12 e[++c]=(node){y,h[x]};h[x]=c; 13 e[++c]=(node){x,h[y]};h[y]=c; 14 } int dfs(int x,int f){ 15 fa[x][0]=f;rt[x]=x;++cnt; 16 for(int i=1;i<=19;i++) 17 fa[x][i]=fa[fa[x][i-1]][i-1]; 18 for(int i=h[x],y;i;i=e[i].nxt) 19 if((y=e[i].y)!=f) d[y]=d[x]+1,dfs(y,x); 20 } int lca(int x,int y){ 21 if(d[x]<d[y]) swap(x,y); 22 for(int i=19;~i;i--) 23 if(d[fa[x][i]]>=d[y]) x=fa[x][i]; 24 if(x==y) return x; 25 for(int i=19;~i;i--){ 26 if(fa[x][i]!=fa[y][i]) 27 x=fa[x][i],y=fa[y][i]; 28 } return fa[x][0]; 29 } int pushup(int x){ 30 t[x].sm=t[lc(x)].sm+t[rc(x)].sm; 31 } int update(int &x,int l,int r,int p,int v){ 32 if(!x) x=++cnt;if(l==r){ 33 t[x].sm+=v;return 0; 34 } int mid=l+r>>1; 35 if(p<=mid) update(lc(x),l,mid,p,v); 36 else update(rc(x),mid+1,r,p,v); 37 pushup(x);return 0; 38 } int query(int x,int l,int r,int p){ 39 if(l==r) return t[x].sm;int mid=l+r>>1; 40 if(p<=mid) return query(lc(x),l,mid,p); 41 else return query(rc(x),mid+1,r,p); 42 } int merge(int x,int y,int l,int r){ 43 if(!x||!y) return x|y;int mid=l+r>>1; 44 if(l==r){ 45 t[x].sm+=t[y].sm;return x; 46 } t[x].l=merge(lc(x),lc(y),l,mid); 47 t[x].r=merge(rc(x),rc(y),mid+1,r); 48 pushup(x);return x; 49 } int solve(int x,int f){ 50 for(int i=0;i<p1[x].size();i++) 51 update(rt[x],0,inf,p1[x][i],1); 52 for(int i=0;i<p2[x].size();i++) 53 update(rt[x],0,inf,p2[x][i],-1); 54 for(int i=h[x],y;i;i=e[i].nxt) 55 if((y=e[i].y)!=f) solve(y,x), 56 merge(rt[x],rt[y],0,inf); 57 if(d[x]+n-q[x]>=0) ans[x]+= 58 query(rt[x],0,inf,d[x]+n-q[x]); 59 if(d[x]+n+q[x]<=inf&&q[x]) ans[x]+= 60 query(rt[x],0,inf,d[x]+n+q[x]); 61 } int main(){ 62 scanf("%d%d",&n,&m); 63 for(int i=1,x,y;i<n;i++) 64 scanf("%d%d",&x,&y),add(x,y); 65 for(int i=1;i<=n;i++) 66 scanf("%d",&q[i]);d[1]=1;dfs(1,0); 67 for(int i=1,x,y;i<=m;i++){ 68 scanf("%d%d",&x,&y);int a=lca(x,y); 69 p1[x].push_back(d[x]+n); 70 p1[y].push_back(n+2*d[a]-d[x]); 71 p2[a].push_back(d[x]+n); 72 p2[fa[a][0]].push_back(n+2*d[a]-d[x]); 73 } solve(1,0);for(int i=1;i<=n;i++) 74 printf("%d ",ans[i]);return 0; 75 }
线段树合并
转载于:https://www.cnblogs.com/Alan-Luo/p/10396193.html
NOIP2016 天天爱跑步 线段树合并相关推荐
- [NOIp2016]天天爱跑步 线段树合并
[NOIp2016]天天爱跑步 LG传送门 作为一道被毒瘤出题人们玩坏了的NOIp经典题,我们先不看毒瘤的"动态爱跑步"和"天天爱仙人掌",回归一下本来的味道. ...
- BZOJ 4719: [Noip2016]天天爱跑步 线段树合并
title BZOJ 4719 LUOGU 1600 简化题意: 小c同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.<天天爱跑步>是一个养成类游戏,需要玩家每 ...
- 【NOIP2016】【桶/线段树合并】【树上差分】天天爱跑步
[题目描述] [思路] 这是道好题呀.考虑把一条路径(u,v)拆成两条:从u到lca(u,v),从lca(u,v)到v.下面我们以向上的路径为例讨论做法.对于一条向上的路径,它对一个点x有贡献当且仅当 ...
- [noip2016]天天爱跑步(主席树+lca)
恩..在百度的第一页翻了翻,没有用主席树做的,于是打算水一篇blog: 天天爱跑步 首先有一个解题的关键: 将玩家的向上和向下分成两部分: 先定义几个变量,s是起点,t是终点,wi是每个观察员出现的时 ...
- [2021.1.27多校省选模拟10]跑步(线段树合并)
[2021.1.27多校省选模拟10]跑步 经典的树上启发式合并题目,维护对应子树的从当前点到子树内一个节点这个链待定,其他部分已经确定的方案数,这个东西按照对应点到根节点的路径点权和为下标存在一个权 ...
- NOIP2016天天爱跑步
NOIP2016天天爱跑步 这题一看显然lca+树上差分,但是因为有w的限制不能直接加,所以考虑权值线段树合并, 每个选手的起点终点对于不同的节点的影响是不同的,这就非常麻烦了,但是可以发现无论如何他 ...
- 线段树合并:从入门到放弃
感谢这篇博客(这里跳转)以及邱宇大神的讲解,我也算作入门(自闭)了. 需要掌握的前置知识点:动态开点线段树.权值线段树. 一.合并思想 线段树合并,就是指建立一颗新的线段树,保存原有的两颗线段树的信息 ...
- [NOIP2016]天天爱跑步 题解(树上差分) (码长短跑的快)
Description 小c同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.<天天爱跑步>是一个养成类游戏,需要 玩家每天按时上线,完成打卡任务.这个游戏的地图 ...
- 线段树合并(四道例题)
顾名思义,就是合并两个同构(就是维护的区间长度一样)线段树,其实也没啥比较nb的算法,就是一个一个节点的合并,但是如果在n个要合并的线段树里,如果一共有m个元素,则配合动态开点,复杂度会均摊成一个惊人 ...
最新文章
- mockito_使用FizzBu​​zz和Mockito进行单元测试
- MAT之PSO:利用PSO算法优化二元函数,寻找最优个体适应度
- css3的动画详解 html直接可以运行
- 2019年各大银行最新存款利率,这么存可以获得更多的利息!
- 死锁的产生、预防和避免
- 420集的python教程下载_阿里达摩院推荐的420集的python教程,据说懂中文就能入门高清版...
- 计算机求百钱买百鸡采用的算法,多种解法求百钱百鸡问题.doc
- 就问你慌不慌:每天都有 一百万 程序员在投简历
- Giroro制造武器
- Oracle中“行转列”的实现方式
- jQuery活动倒计时插件
- 金鹰卡通java面试_两则电视栏目招募通告,来试试?!
- Zookeeper总结——知识点、选举机制、客户端操作及写数据流程、API操作、zookeeper分布式锁之 Curator、ZAB协议、CAP理论之zookeeper的CP理论
- 野火指南者ESP8266模块学习
- 面试后说hold什么意思_为什么面试完,总是让你回去等通知?
- 怎样取消php加密mppe,PHP 加密问题 求大神帮忙? 谢谢
- ps怎么抠地图线路_用PS怎么抠地图?
- 换一种姿势挖掘任意用户密码重置漏洞
- 【jsPlumb】使用记录
- python 实现csdn平台自动化定时评论功能实现
热门文章
- easy-rules规则引擎最佳落地实践
- 2110-微服务核心知识点及问题分析
- Windows系统下载SRA数据,使用sratoolkit工具
- php制作日历带节日实验目的,PHP做日历
- 【Microsoft Azure 的1024种玩法】四. 利用Azure Virtual machines 打造个人专属云盘,速度吊打某云盘...
- 为什么输入百度的IP地址不能直接访问
- 2021年2月中国编程语言排行榜
- 集合底层源码分析之HashMap《上》(三)
- Linux图形化磁盘管理工具gparted
- getservbyname()函数与getservbyport()函数