Codeforces 343D Water Tree & 树链剖分教程
题目大意
给定一棵根为1,初始时所有节点值为0的树,进行以下三个操作:
*将以某点为根的子树节点值都变为1
*将某个节点及其祖先的值都变为0
*询问某个节点的值
解题思路
这是一道裸的树链剖分题。下面详细地介绍一下树链剖分。
树链剖分预备知识:
线段树、DFS序
树链剖分想法|起源
首先,如果一棵树退化成一条链,那么它会有非常好的性质。我们可以用线段树等数据结构来维护相关操作,使得效率更高。那么我们考虑一般的树,它是否能被分成一些链,使它们也能更高效地进行某些操作?
算法流程
以下以点带权的树为例,介绍最为常用的轻重边剖分。
我们令当前点到它儿子中size最大的点的边为重边,其余为轻边。
由此定义,我们可以得到以下结论:
*若u(父)到v(子)的边为轻边,则size(u) >= 2 * size(v)【1】(结论平凡)
*从根节点到某叶节点的轻边数不超多log(n)【2】(由上条结论易得)
接下来,我们定义由重边组成的链叫重链。那么重链又有以下几条性质:
*根节点到某叶节点的重链数不超过log(n)【3】(由于重链的头或尾都是轻边)
*任何一个节点在且仅在一条重链上【4】
那么我们对树作修改的时候只需要对重链上的点作修改(由性质【4】)。那么我们可以尝试将原树拉成一条链,同时使得重链上的点在拉成后的链上连续。
我们可以用两遍dfs来实现这个操作。第一步,我们求出每个点的父亲信息(father),深度信息(depth),子树大小(size)和重儿子(son)(这么叫应该可以)。第二遍求出每个点的所在重链的顶部节点(top),当前点在线段树上位置(seg),同时求出线段树上点对应的点的编号(rev)。
代码实现(其中用数组模拟链表保存原树信息):
int father[MAXN], dep[MAXN], size[MAXN], son[MAXN], top[MAXN], seg[MAXN], rev[MAXN];
int used_space = 0;
void first_dfs(int fa, int pos){
father[pos] = fa;
dep[pos] = dep[fa] + 1;
size[pos] = 1;
for(int t = f[pos]; t; t = nex[t]){
if(lin[t] == fa) continue;
first_dfs(pos, lin[t]);
size[pos] += size[lin[t]];
if(size[lin[t]] > size[son[pos]]) son[pos] = lin[t];//选取size最大的为重儿子
}
return;
}
void second_dfs(int fa, int pos){
if(size[pos] > 1){//为了使重链在线段树上连续,所以先访问重儿子
++used_space;
seg[son[pos]] = used_space;
top[son[pos]] = top[pos];
rev[used_space] = son[pos];
second_dfs(pos, son[pos]);
}
for(int t = f[pos]; t; t = nex[t])
if(lin[t] != fa && lin[t] != son[pos]){
++used_space;
seg[lin[t]] = used_space;
top[lin[t]] = lin[t];
rev[used_space] = lin[t];
second_dfs(pos, lin[t]);
}
return;
}
//主程序调用
first_dfs(1, 1);
top[1] = 1; seg[1] = 1; rev[1] = 1; used_space++;
second_dfs(1, 1);
接下来考虑查询/修改操作。
第一类是在某两点之间的路径上操作。设这两点为x,y,则此问题实则考虑x->lca(x,y)->y。
若top[x] != top[y], 那么它们不在同一条链上。此时我们不如假设top[x]的深度较深,那么top[x]一定不为lca(x,y)。那么我们可以让x跳到father[top[x]],同时在线段树中处理seg[top[x]]->seg[x]这段区间。不断这样地操作,直到top[x]==top[y]。现在,x, y就在同一条链上了。不妨还是设x的深度较深,那么我们只需在线段树中seg[y]->seg[x]这段区间。
代码实现(以最大值询问为例):
int ask_maxnum(int x, int y){
int fx = top[x], fy = top[y], ans = -INF;
while(fx != fy){
if(dep[fx] < dep[fy]){
swap(x, y);
swap(fx, fy);
}
ans = max(ans, segask_maxnum(seg[fx], seg[x], 1, 1, used_space));
x = father[fx]; fx = top[x];
}
if(dep[x] < dep[y]) swap(x, y);
ans = max(ans, segask_maxnum(seg[y], seg[x], 1, 1, used_space));
return ans;
}
还有一类是对某子树的操作。
我们由dfs序可知,以x为根的子树在线段树上为seg[x]->seg[x]+size[x]-1这一段区间。代码实现较为简单,这里就不详细贴出。
还有单点的修改与查询就不用多说了吧:)
建议习题
模板题:洛谷P3384
题目CF343D代码:
#include<bits/stdc++.h>
#define mid ((l + r) >> 1)
using namespace std;
const int MAXN = 500010;
int n, m, x, y, X, Y, tag;
int lp = 0, f[MAXN], lin[MAXN << 1], nex[MAXN << 1];
void add(int x, int y){ lin[++lp] = y; nex[lp] = f[x]; f[x] = lp; return; }
int father[MAXN], deep[MAXN], son[MAXN], size[MAXN], top[MAXN], seg[MAXN], rev[MAXN];
int used = 0, tree[MAXN << 3]; //0 : all empty; 1 : all filled; 2 : unknow;
int get_int(){
char ch = getchar();
while(ch < '0' || ch > '9') ch = getchar();
int t = 0;
while(ch >= '0' && ch <= '9'){
t = t * 10 + ch - '0';
ch = getchar();
}
return t;
}
void dfs1(int fa, int pos){
father[pos] = fa;
deep[pos] = deep[fa] + 1;
size[pos] = 1;
for(int t = f[pos]; t; t = nex[t])
if(lin[t] != fa){
dfs1(pos, lin[t]);
size[pos] += size[lin[t]];
if(size[lin[t]] > size[son[pos]]) son[pos] = lin[t];
}
return;
}
void give_(int x){
used++;
seg[x] = used;
rev[used] = x;
return;
}
void dfs2(int fa, int pos){
if(size[pos] > 1){
top[son[pos]] = top[pos];
give_(son[pos]);
dfs2(pos, son[pos]);
}
for(int t = f[pos]; t; t = nex[t])
if(lin[t] != fa && lin[t] != son[pos]){
top[lin[t]] = lin[t];
give_(lin[t]);
dfs2(pos, lin[t]);
}
return;
}
void tag_down(int pos, int l, int r){
if(l == r) return;
if(tree[pos] == 2) return;
tree[pos << 1] = tree[pos];
tree[(pos << 1) + 1] = tree[pos];
return;
}
void seg_set(int pos, int l, int r){
if(X <= l && r <= Y){
tree[pos] = tag;
return;
}
if(tree[pos] != 2) tag_down(pos, l, r);
if((tree[pos] != 2) && (tag != tree[pos])) tree[pos] = 2;
if(X <= mid) seg_set(pos << 1, l, mid);
if(Y > mid) seg_set((pos << 1) + 1, mid + 1, r);
return;
}
int seg_ask(int pos, int l, int r){
if(tree[pos] != 2) return tree[pos];
if(X <= mid) return seg_ask(pos << 1, l, mid);
if(X > mid) return seg_ask((pos << 1) + 1, mid + 1, r);
return -1;
}
void fill(int x){
X = seg[x]; Y = X + size[x] - 1; tag = 1;
seg_set(1, 1, used);
return;
}
void empty(int x){
int fx;
while(x){
fx = top[x];
X = seg[fx]; Y = seg[x]; tag = 0;
seg_set(1, 1, used);
x = father[fx];
}
return;
}
int ask(int x){
X = seg[x];
return seg_ask(1, 1, used);
}
int main(){
n = get_int();
for(int i = 1; i < n; i++){
x = get_int(); y = get_int();
add(x, y); add(y, x);
}
dfs1(0, 1); //get first four values
give_(1); top[1] = 1;
dfs2(0, 1);
m = get_int();
for(int i = 1; i <= m; i++){
x = get_int(); y = get_int();
if(x == 1) fill(y); else
if(x == 2) empty(y); else
printf("%d\n", ask(y));
}
return 0;
}
Codeforces 343D Water Tree & 树链剖分教程的更多相关文章
- CodeForces 343D water tree(树链剖分)
Mad scientist Mike has constructed a rooted tree, which consists of n vertices. Each vertex is a res ...
- Codeforces Round #200 (Div. 1) D Water Tree 树链剖分 or dfs序
Water Tree 给出一棵树,有三种操作: 1 x:把以x为子树的节点全部置为1 2 x:把x以及他的所有祖先全部置为0 3 x:询问节点x的值 分析: 昨晚看完题,马上想到直接树链剖分,在记录时 ...
- Codeforces Round #200 (Div. 1) D. Water Tree 树链剖分+线段树
D. Water Tree time limit per test 4 seconds memory limit per test 256 megabytes input standard input ...
- Water Tree(树链剖分+dfs时间戳)
Water Tree http://codeforces.com/problemset/problem/343/D time limit per test 4 seconds memory limit ...
- CF343D Water Tree 树链剖分
问题描述 LG-CF343D 题解 树剖,线段树维护0-1序列 yzhang:用珂朵莉树维护多好 \(\mathrm{Code}\) #include<bits/stdc++.h> usi ...
- Hdu 5274 Dylans loves tree (树链剖分模板)
Hdu 5274 Dylans loves tree (树链剖分模板) 题目传送门 #include <queue> #include <cmath> #include < ...
- POJ3237 Tree 树链剖分 边权
POJ3237 Tree 树链剖分 边权 传送门:http://poj.org/problem?id=3237 题意: n个点的,n-1条边 修改单边边权 将a->b的边权取反 查询a-> ...
- CodeForces 916E Jamie and Tree(树链剖分+LCA)
To your surprise, Jamie is the final boss! Ehehehe. Jamie has given you a tree with n vertices, numb ...
- Query on a tree——树链剖分整理
树链剖分整理 树链剖分就是把树拆成一系列链,然后用数据结构对链进行维护. 通常的剖分方法是轻重链剖分,所谓轻重链就是对于节点u的所有子结点v,size[v]最大的v与u的边是重边,其它边是轻边,其中s ...
随机推荐
- Spring使用注解实现AOP
一.AspectJ概述 AspectJ是一个面向切面的框架,它扩展了Java语言.定义了AOP语法,能够在编译期提供代码的织入,它提供了一个专门的编译期用来生成遵守字节编码规范的Class文件. @A ...
- 链表-简单练习题1-数据结构实验之链表一:顺序建立链表 SDUT2117
Problem Description 输入N个整数,按照输入的顺序建立单链表存储,并遍历所建立的单链表,输出这些数据. Input 第一行输入整数的个数N:第二行依次输入每个整数. Output 输 ...
- 【计算机网络】-介质访问子层-(信道划分介质访问控制&随机访问介质访问控制)
[计算机网络]-介质访问子层-概述 介质访问控制子层功能 解决信道争用的协议,即用于多路访问信道上确定下一个使用者的协议 是数据链路层协议的一部分 介质访问控制子层位置 位于数据链路层的底部! 信道分 ...
- 如何用纯 CSS 创作出平滑的层叠海浪特效
效果预览 在线演示 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/comehope/pen/JvmBdE 可交互视频教 ...
- JS基础_强制类型转换-Boolean
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
- js获取url(request)中的参数
index.htm?参数1=数值1&参数2=数值2&参数3=数据3&参数4=数值4&...... 静态html文件js读取url参数,根据获取html的参数值控制htm ...
- Swift(二)基础部分
数据类型 Swift 包含了 C 和 Objective-C 上所有基础数据类型.它还增加了 Objective-C 中没有的高阶数据类型比如元组(Tuple) 1.基础类型 Int整形和UInt无符 ...
- oracle存储过程临时表
接到一个以前领导的需求,说的大概意思是: 如果能关联上就取关联上的最大值更新到表里,没有关联上的就取原来的值. 写一个存储过程,这正好用到了临时表,上网查询,用的太乱了,特别记录. 准备阶段 创建PD ...
- 2019.9.27PHP基础
PHP 基础语法规范: 1 <?php 开头 ?>结尾 2 php可以单独存在也可以和html等结合使用 3后缀名一般以.php结尾 php4,php5,php6,php7,phtml. ...
- emwin之BUTTON控件显示位图和流位图出现卡顿延迟的情况
@2019-05-16 [问题] 参照Armfly的emwin教程第46章 BUTTON-按钮控件显示位图和流位图,实际使用时导致界面切换卡顿延迟较大的情况 [环境] F429IGT6 + W9825 ...