2020牛客NOIP赛前集训营-提高组(第三场) C - 牛半仙的妹子Tree (树链剖分)
昨天教练问我:你用树剖做这道题,怎么全部清空状态呢? 我:???不是懒标记就完了??? 教练:树剖不是要建很多棵线段树吗,不止log个,你要一个一个清? 我:为什么要建很多棵线段树?不都是一棵树来记录吗?不会还有人树剖每条链都建线段树吧
这里笔者给现在或是以后写树剖的读者们提个建议,写树剖用一个线段树记录足矣,只要保证一条重链上的点在线段树上是按深度连续的(并且一个子树内的点在线段树上是连续的),就不会出问题,好想、好写、还好调
题面
题解
我们可以把问题转换一下,每次就不让mz实质性地扩散状态,而是求距离 x 最近的一开始不理睬的mz距离 x 的距离,这样再判断时间就行,由于每个mz不理睬的起始时间是不一样的,所以我们可以上溯到 0 时刻,求等价的、 0 时刻开始不理睬的最近距离。
举个例子,对于 x ,原本在其上方(来自祖先的方向)的点就等价地往上移动 ti 距离,比如 a 时刻加进来一个与 x 的距离为 b 的mz(在 x 上方,不一定非要是祖先),那么就等价于 0 时刻加进来一个深度为 depth[x] - b - a 的 x 的祖先;
原本在 x 下方(朝向叶子的方向)的点就等价地往下移动 ti 距离,比如 a 时刻加进来一个深度为 b 的mz(在 x 的子树中),那么就等价于 0 时刻加进来一个深度为 b + a 的 x 的后代。
很明显祖先和后代的情况是截然不同的,我们可以分开维护,用树链剖分,在线段树上维护子树和链的最值。
后代方向
对于后代,我们每次加入"一个点的深度+当前时刻"到树上(单点修改),在线段树上维护 sn (Subtree Number) 为该值的最小值(显然这里越小越近),询问的时候访问子树中的 sn 然后再减去 depth[x] 就是子树中最近的距离了。
祖先方向
对于来自祖先方向,就比较复杂。树上比较远的两个点,实际上是要绕过lca才能扩散到的,
我们把右边折上去
相当于从祖先方向跑下来,我们把这个 depth' 减去加进来的时刻,存到 lca 里,维护最大值(这里则是越大越近)。
但是,这么来说,加入一个点 i 的时候,i 的每个祖先都要修改。所以解决方法就是区间修改,每次给一条链(重链的子链)集体加入 depth[i]+time(加入的是在子树中的等价起点了,何如?),线段树里则记录 depth' - time 的最大值,记为 an(Ancestor Number)。所以在线段树里就需要用懒标记维护 an:
懒标记 lza (Lazy mark of Ancestor Number) 记录 depth[i]+time 的最小值,跟 sn 类似,但是记录的是不同的东西。把 lza 重现在 an 里就是这条链中深度最大的点的深度 depth 减去 (lza - depth) :
void ada(int a,int anc) {//加入anc = depth[i] + time
tre[a].an = max(tre[a].an,d[id[tre[a].r]] - (anc - d[id[tre[a].r]]));//d 即 depth,id表示线段树上位置对应的原树上节点编号
tre[a].lza = min(tre[a].lza,anc);
}
用深度最大的点(右端点)更新 an ,是因为这个修改操作特殊点在于,lza 一定大于链上的每一个点的深度,我们是更新 i 的祖先嘛,所以最下面(深度最大)的那个祖先用来更新肯定是最合适的,于是这样就可以维护了 an 了。
询问的时候,只需要 depth[x] - 从 x 到根的链上的 an 就是来自祖先方向的最短距离。
整合
询问点的时候,把上述两个方向求得的距离取最小值,然后减去当前时间,若小于等于 0 则已被扩散到。
对于初始化,需要把 an 赋为 -INF,把 sn 和 lza 赋为 INF。
最后它有一个清空操作,这个在线段树上用一个清空标记就行了,应该很简单的(前提是只建一棵线段树)。
CODE
#include<map>
#include<queue>
#include<cmath>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 100005
#define LL long long
#define DB double
#define ENDL putchar('\n')
#define lowbit(x) ((-x)&(x))
LL read() {
LL f = 1,x = 0;char s = getchar();
while(s < '0' || s > '9') {if(s=='-')f=-f;s = getchar();}
while(s >= '0' && s <= '9') {x=x*10+(s-'0');s=getchar();}
return f*x;
}
const int MOD = 1000000007;
int n,m,i,j,s,o,k;
vector<int> g[MAXN];
int d[MAXN],fa[MAXN],siz[MAXN],son[MAXN],tp[MAXN],dfn[MAXN],cnt,rr[MAXN],id[MAXN];
struct tr{
int l,r;
int an,sn;//max,min
int lza,lzc;//min,---
tr(){l = r = lzc = 0;sn = lza = 0x3f3f3f3f;an = -0x3f3f3f3f;}
}tre[MAXN<<3];
void maketree(int a,int L,int R) {
tre[a].l = L;tre[a].r = R;
if(L < R) {
int mid = (L + R) >> 1;
maketree(a<<1,L,mid);
maketree(a<<1|1,mid+1,R);
}return ;
}
void upd(int a) {
tre[a].an = max(tre[a<<1].an,tre[a<<1|1].an);
tre[a].sn = min(tre[a<<1].sn,tre[a<<1|1].sn);
}
void ada(int a,int anc) {
tre[a].an = max(tre[a].an,d[id[tre[a].r]] - (anc - d[id[tre[a].r]]));
tre[a].lza = min(tre[a].lza,anc);
}
void cle(int a) {tre[a].an = -0x3f3f3f3f;tre[a].sn = tre[a].lza = 0x3f3f3f3f;tre[a].lzc = 1;}
void pus(int a) {
if(tre[a].l == tre[a].r) return ;
if(tre[a].lzc) {
cle(a<<1);cle(a<<1|1);
tre[a].lzc = 0;
}
if(tre[a].lza < 0x3f3f3f3f) {
ada(a<<1,tre[a].lza);
ada(a<<1|1,tre[a].lza);
tre[a].lza = 0x3f3f3f3f;
}
return ;
}
void adds(int a,int ad,int son) {
if(tre[a].l > ad || tre[a].r < ad) return ;
if(tre[a].l == tre[a].r) {tre[a].sn = min(tre[a].sn,son);return ;}
pus(a);
adds(a<<1,ad,son);adds(a<<1|1,ad,son);
upd(a); return ;
}
void adda(int a,int l,int r,int anc) {
if(tre[a].l > r || tre[a].r < l) return ;
if(tre[a].l >= l && tre[a].r <= r) {ada(a,anc);return ;}
pus(a);
adda(a<<1,l,r,anc);adda(a<<1|1,l,r,anc);
upd(a); return ;
}
int findsons(int a,int l,int r) {
if(tre[a].l > r || tre[a].r < l) return 0x3f3f3f3f;
if(tre[a].l >= l && tre[a].r <= r) {return tre[a].sn;}
pus(a); return min(findsons(a<<1,l,r),findsons(a<<1|1,l,r));
}
int findance(int a,int l,int r) {
if(tre[a].l > r || tre[a].r < l) return -0x3f3f3f3f;
if(tre[a].l >= l && tre[a].r <= r) {return tre[a].an;}
pus(a); return max(findance(a<<1,l,r),findance(a<<1|1,l,r));
}
//-------------------------------------------
void dfs0(int x) {//d[],fa[],siz[],son[]
d[x] = d[fa[x]] + 1;
siz[x] = 1;
son[x] = 0;
for(int i = 0;i < (int)g[x].size();i ++) {
int y = g[x][i];
if(y != fa[x]) {
fa[y] = x;
dfs0(y);
siz[x] += siz[y];
if(siz[y] > siz[son[x]]) {
son[x] = y;
}
}
}return ;
}
void dfs(int x) {//tp[],dfn[],rr[],id[]
dfn[x] = ++ cnt;
id[cnt] = x;
if(son[fa[x]] == x) tp[x] = tp[fa[x]];
else tp[x] = x;
if(son[x]) dfs(son[x]);
for(int i = 0;i < (int)g[x].size();i ++) {
if(g[x][i] != fa[x] && g[x][i] != son[x]) {
dfs(g[x][i]);
}
}
rr[x] = cnt;
return ;
}
void addx(int a,int ti) {
adds(1,dfn[a],d[a] + ti);
int fn = tp[a],da = d[a] + ti;
while(fn) {
adda(1,dfn[fn],dfn[a],da);
a = fa[fn];fn = tp[a];
}
return ;
}
int query(int a) {
int dis = findsons(1,dfn[a],rr[a]) - d[a];
int fn = tp[a],da = d[a];
while(fn) {
dis = min(dis,da - findance(1,dfn[fn],dfn[a]));
a = fa[fn];fn = tp[a];
}
return dis;
}
int main() {
n = read();m = read();
for(int i = 1;i < n;i ++) {
s = read();o = read();
g[s].push_back(o);
g[o].push_back(s);
}
dfs0(1);
dfs(1);
maketree(1,1,cnt);
int tim = 0;
while(m --) {
k = read();s = read();
if(k == 1) {
addx(s,tim);
}
else if(k == 2) {
cle(1);
}
else if(k == 3) {
int md = query(s);
printf((md - tim <= 0) ? "wrxcsd\n":"orzFsYo\n");
}
tim ++;
}
return 0;
}
2020牛客NOIP赛前集训营-提高组(第三场) C - 牛半仙的妹子Tree (树链剖分)的更多相关文章
- 2020牛客NOIP赛前集训营-提高组(第三场)C-牛半仙的妹子Tree【虚树,最短路】
正题 题目链接:https://ac.nowcoder.com/acm/contest/7609/C 题目大意 给出\(n\)个点的一棵树,\(m\)个时刻各有一个操作 标记一个点,每个点被标记后的每 ...
- 2020牛客NOIP赛前集训营-提高组(第二场)- B.包含 (FWT)
题面 题解 这题就是个快速沃尔什变换的模板题,输入ai时,令s[ai]=1,对s[]做一遍DWT_AND(s)(快速沃尔什正变换,按位与),然后直接访问s[x]完事. #include<map& ...
- 2020牛客NOIP赛前集训营-普及组(第二场)A-面试
面 试 面试 面试 题目描述 牛牛内推了好多人去牛客网参加面试,面试总共分四轮,每轮的面试官都会对面试者的发挥进行评分.评分有 A B C D 四种.如果面试者在四轮中有一次发挥被评为 D,或者两次发 ...
- 2020牛客NOIP赛前集训营-普及组(第二场) 题解
目录 T1 面试 描述 题目描述 输入描述: 输出描述: 题解 代码 T2 纸牌游戏 描述 题目描述 输入描述: 输出描述: 题解 代码 T3 涨薪 描述 题目描述 输入描述: 输出描述: 题解 代码 ...
- 2022牛客OI赛前集训营-提高组(第一场) 奇怪的函数 根号很好用
奇怪的函数 考虑暴力,每次查询\(O(n)\)扫所有操作,修改\(O(1)\) 这启发我们平衡复杂度,考虑分块. 观察题目性质,可以发现,经过若干次操作后得到的结果一定是一个关于\(x\)的分段函数, ...
- 牛客网NOIP赛前集训营-普及组(第二场)和 牛客网NOIP赛前集训营-提高组(第二场)解题报告
目录 牛客网NOIP赛前集训营-普及组(第二场) A 你好诶加币 B 最后一次 C 选择颜色 D 合法括号序列 牛客网NOIP赛前集训营-提高组(第二场) A 方差 B 分糖果 C 集合划分 牛客网N ...
- 牛客网NOIP赛前集训营-提高组(第一场)
牛客的这场比赛感觉真心不错!! 打得还是很过瘾的.水平也比较适合. T1:中位数: 题目描述 小N得到了一个非常神奇的序列A.这个序列长度为N,下标从1开始.A的一个子区间对应一个序列,可以由数对[l ...
- 牛客网NOIP赛前集训营-提高组(第二场)A 方差
链接:https://www.nowcoder.com/acm/contest/173/A来源:牛客网 题目描述 一个长度为 m 的序列 b[1...m] ,我们定义它的方差为 ,其中 表示序列的平 ...
- [牛客网NOIP赛前集训营-提高组(第一场)]C.保护
链接:https://www.nowcoder.com/acm/contest/172/C来源:牛客网 题目描述 C国有n个城市,城市间通过一个树形结构形成一个连通图.城市编号为1到n,其中1号城市为 ...
随机推荐
- Tarjan算法模板(USACO03FALL受欢迎的牛)
好文章 #include<bits/stdc++.h> using namespace std; const int N = 10010, M = 50010; int n, m; int ...
- vue根据后端菜单自动生成路由(动态路由)
vue根据后端菜单自动生成路由(动态路由) router.js import Vue from 'vue' import Router from 'vue-router' import store f ...
- flink-执行模式
flink的执行模式 flink既能处理离线数据,也能处理实时数据,在1.12.0版本以前,批数据返回的数据集合是dataSet,对应一套dataSet的api,从1.12.0版本以后,flink实现 ...
- python 基础知识-day6(内置函数)
1.sorted():用于字典的排序 dict1={"name":"cch","age":"3","sex&q ...
- 记一次beego通过go get命令后找不到bee.exe的坑
学习goweb开发,gin是个轻量级的框架.如果想要一个类如aspnetmvc帮我们搭建好了的goweb框架,beego值得去学习.否则gin下面需要动手构建好多代码.新手还是先学现成的节约时间成本. ...
- VisonPro · 视觉定位工具包示例
一.概述 视觉定位工具包一般包含: 1.相机取像: 2.图像九点标定: 3.Mark点粗定位: 4.建立粗定位坐标系: 5.Mark点精定位 6.输出Mark点坐标,角度等信息. 二.分类 1.单特征 ...
- 挑战30天写操作系统-day3-进入32位模式并导入C语言
目录 1.制作真正的IPL IPL:启动区,启动程序装载器完整代码: ; haribote-ipl ; TAB=4 CYLS EQU 10 ; 声明CYLS=10 ORG 0x7c00 ; 指明程序装 ...
- ESP分区重建,解决各种引导问题
电脑装了双系统,win7和win10,每次重启都进入不同系统,郁闷至极,索性把不常用的Win7盘格式化,但依旧解决不了问题.所以有了以下方法. 1.进PE删除ESP分区(先备份). 2.新建ESP分区 ...
- ICMP 介绍
简介 ICMP(Internet 控制报文协议,Internet Control Message Protocol , RFC 792).主要用于在IP主机与路由器之间传递控制消息,用于报告主机是否可 ...
- Airbnb的动态kubernetes集群扩缩容
Airbnb的动态kubernetes集群扩缩容 本文介绍了Airbnb的集群扩缩容的演化历史,以及当前是如何通过Cluster Autoscaler 实现自定义扩展器的.最重要的经验就是Airbnb ...