树链剖分的一种妙用与一类树链修改单点查询问题的时间复杂度优化——2018ACM陕西邀请赛J题
题目描述
有一棵树,每个结点有一个灯(初始均是关着的)。每个灯能对该位置和相邻结点贡献1的亮度。现有两种操作:
(1)将一条链上的灯状态翻转,开变关、关变开;
(2)查询一个结点的亮度。
数据规模:\(1 \le n,q \le 10^5\)
简要题解
对于这种题,很容易想到任意指定一个根转化为有根树,每个结点维护值\(a_i\)表示它的所有儿子的贡献之和,这样再加上自己以及父亲的贡献就能回答一个询问了。
然而经过一波思考发现问题在于链修改时根本没法维护\(a_i\)。因此需要用链修改时的常规操作——树链剖分理论来解决本题了。
对于树链剖分,每一条链被剖分成了\(O(\log n)\)条重链,下面考虑修改每条重链\((s,t)\),不妨设\(depth[s]<depth[t]\)。可以发现修改后只会改变\((father[s],father[t])\)这条链上的\(a_i\),但是注意到链\((s,father[t])\)上每个结点都是它父亲的重儿子,如果我们\(a_i\)不维护重儿子,让\(a_i\)表示它的所有轻儿子的贡献之和,那么每条重链只需暴力修改一个\(a_{father[s]}\)。这样对于每个修改,更新\(a\)数组的时间复杂度为\(O(\log n)\);同时当然还需要更新链上灯的状态,即进行链异或1的操作,如果采用树链剖分加树状数组可达到\(O(\log ^2 n)\)的时间复杂度。对于每个询问\(x\),只需要查询\(a_x\)(对应轻儿子),以及\(father[x],x,son[x]\)(分别对应父亲、重儿子、自身)的状态即可(这里\(son[x]\)表示\(x\)的重儿子),查询时间复杂度\(O(\log n)\)。
总时间复杂度\(O(n+q \log ^2 n)\)。(PS:感谢队友ddd教我这么巧的方法!)
进一步的改进
这一做法虽然很巧妙,但是其唯一的瓶颈\(O(\log ^2 n)\)为树状数组修改,其它所有操作(无论\(a_i\)修改还是树状数组查询)均为\(O(\log n)\),显得很不协调(强迫症患者的我当然不舒服了)。针对本题链上异或单点查询问题,这里再给出一个更为高效的做法。
构造一个DFS序列,对每棵子树\(i\),序列定义为\(i\)开头,然后是\(i\)的每棵子树的DFS序列依次拼起来,最后\(i\)结尾,这样序列总长度为\(2n\)。设\(h_1[i]\)和\(h_2[i]\)分别为\(i\)第一次和第二次在序列中出现的位置。维护一个长为\(2n\)的数组\(b\),对于每次链\((x,y)\)异或1,执行如下算法:
(1)将\(b[h_2[x]]\)到\(b[2n]\)全部异或1;
(2)将\(b[h_2[y]]\)到\(b[2n]\)全部异或1;
(3)将\(b[h_2[lca(x,y)]]\)异或1。
定理:对于上述算法,每次单点查询\(x\)时,只需求\(b[h_1[x]] \oplus b[h_2[x]]\)即为答案。
为证明该定理,首先证明以下引理:
引理1:若结点\(t\)是结点\(x\)的祖先(可以是自身),当且仅当\(h_1[t] \le h_1[x]\)且\(h_2[t] \ge h_2[x]\)。
根据DFS序列的定义,证明显然。
引理2:对于结点\(x\),将\(b[h_2[x]]\)到\(b[2n]\)全部异或1后,只有\(x\)祖先\(t\)(包括自身)的\(b[h_1[t]] \oplus b[h_2[t]]\)值改变;
证明:首先证明充分性。若结点\(t\)是结点\(x\)的祖先,根据引理1,\(b[h_1[t]]\)不变,\(b[h_2[t]]\)异或了1,正确;
接下来证明必要性。若结点\(t\)不是结点\(x\)的祖先,根据引理1,分三种情况:
情况1:\(h_1[t] \le h_1[x]\)且\(h_2[t] < h_2[x]\),此时\(b[h_1[t]]\)和\(b[h_2[t]]\)均不变;
情况2:\(h_1[t] > h_1[x]\)且\(h_2[t] \ge h_2[x]\),此时必有\(h_1[t] > h_2[x]\)。若不然,根据\(h_2[x] \gt h_1[t]\)以及\(h_2[x] \le h_2[t]\)可知\(t\)是\(x\)的祖先,根据引理1,这与 \(h_1[t] > h_1[x]\)矛盾。因此\(b[h_1[t]]\)和\(b[h_2[t]]\)均异或了1;
情况3:\(h_1[t] > h_1[x]\)且\(h_2[t] < h_2[x]\),由于\(h_1[t] < h_2[t]\),故\(h_1[t] < h_2[x]\),此时\(b[h_1[t]]\)和\(b[h_2[t]]\)均不变。
以上三种情况\(b[h_1[t]] \oplus b[h_2[t]]\)均不变,必要性证毕。事实上,以上三种情况画图恰好对应了\(x\)左侧结点、\(x\)右侧结点和\(x\)的子树。
定理证明:根据引理2易得。
实现时,只需维护树状数组即可,每次链修改的时间复杂度为\(O(\log n)\)。这样整个题目的时间复杂度便被优化到了\(O(n+q \log n)\),已经达到了时间复杂度的极限,不可能再优了。
事实上,上述方法做出适当的修改可用于树上链加,单点求和的情形。广义来说,只要运算构成一个群,都可以用此方法优化时间复杂度。
AC代码
以下代码是改进之前的,更好写一些。
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
vector<int> v[];
int father[], depth[], top[], id[], son[];
int cnt;
int dfs1(int i, int fa)
{
father[i] = fa;
depth[i] = depth[fa] + ;
son[i] = ;
int ret = , maxSize = ;
for (unsigned int j = ; j < v[i].size(); j++){
int t = v[i][j];
if (t == fa)continue;
int size = dfs1(t, i);
ret += size;
if (size > maxSize){
maxSize = size;
son[i] = t;
}
}
return ret + ;
}
void dfs2(int i, int tp)
{
top[i] = tp;
id[i] = ++cnt;
if (son[i])dfs2(son[i], tp);
for (unsigned int j = ; j < v[i].size(); j++){
int t = v[i][j];
if (t != father[i] && t != son[i])dfs2(t, t);
}
}
void init()
{
cnt = ; depth[] = ;
dfs1(, ); dfs2(, );
} bool tree[];
int light[], treeLen;
int sum(int i)
{
int ret = ;
for (; i > ; i -= i&-i)ret ^= tree[i];
return ret;
}
void flip(int i)
{
for (; i <= treeLen; i += i&-i)
tree[i] ^= ;
}
void modify(int s, int t)
{
int top1 = top[s], top2 = top[t];
while (top1 != top2){
if (depth[top1] < depth[top2]){
flip(id[top2]); flip(id[t] + );
t = father[top2];
if (son[t] != top2)light[t] += sum(id[top2]) ? : -;
top2 = top[t];
}
else{
flip(id[top1]); flip(id[s] + );
s = father[top1];
if (son[s] != top1)light[s] += sum(id[top1]) ? : -;
top1 = top[s];
}
}
if (depth[s] > depth[t])swap(s, t);
flip(id[s]); flip(id[t] + );
if (son[father[s]] != s)light[father[s]] += sum(id[s]) ? : -;
}
int main()
{
int n, m, x, y, z;
scanf("%d%d", &n, &m);
for (int i = ; i < n; i++){
scanf("%d%d", &x, &y);
v[x].push_back(y);
v[y].push_back(x);
}
init(); treeLen = n;
while (m--){
scanf("%d", &z);
if (z == ){
scanf("%d%d", &x, &y);
modify(x, y);
}
else{
scanf("%d", &x);
int ans = father[x] ? sum(id[father[x]]) : ;
if (son[x])ans += sum(id[son[x]]);
ans += sum(id[x]) + light[x];
printf("%d\n", ans);
}
}
}
树链剖分的一种妙用与一类树链修改单点查询问题的时间复杂度优化——2018ACM陕西邀请赛J题的更多相关文章
- hdu 3966 Aragorn's Story(树链剖分+区间修改+单点查询)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3966 题意:给一棵树,并给定各个点权的值,然后有3种操作: I C1 C2 K: 把C1与C2的路径上 ...
- HDU 3966 Aragorn's Story (树链点权剖分,成段修改单点查询)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3966 树链剖分的模版,成段更新单点查询.熟悉线段树的成段更新的话就小case啦. //树链剖分 边权修 ...
- 【树状数组区间修改单点查询+分组】HDU 4267 A Simple Problem with Integers
http://acm.hdu.edu.cn/showproblem.php?pid=4267 [思路] 树状数组的区间修改:在区间[a, b]内更新+x就在a的位置+x. 然后在b+1的位置-x 树状 ...
- 【树链剖分】【dfs序】【线段树】bzoj2836 魔法树
这道题告诉我们:树链剖分的重标号就是dfs序. #include<cstdio> #include<algorithm> using namespace std; #defin ...
- BZOJ4999:This Problem Is Too Simple!(DFS序&树上差分&线段树动态开点:区间修改单点查询)
Description 给您一颗树,每个节点有个初始值. 现在支持以下两种操作: 1. C i x(0<=x<2^31) 表示将i节点的值改为x. 2. Q i j x(0<=x&l ...
- POJ2155 Matrix(二维树状数组||区间修改单点查询)
Given an N*N matrix A, whose elements are either 0 or 1. A[i, j] means the number in the i-th row an ...
- HDU 5861 Road(线段树 区间修改 单点查询)
Road Time Limit: 12000/6000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)Total Submi ...
- 【树状数组区间修改单点查询】HDU 4031 Attack
http://acm.hdu.edu.cn/showproblem.php?pid=4031 [题意] 有一个长为n的长城,进行q次操作,d为防护罩的冷却时间,Attack表示区间a-b的墙将在1秒后 ...
- D - Mayor's posters POJ - 2528 离散化+线段树 区间修改单点查询
题意 贴海报 最后可以看到多少海报 思路 :离散化大区间 其中[1,4] [5,6]不能离散化成[1,2] [2,3]因为这样破坏了他们的非相邻关系 每次离散化区间 [x,y]时 把y+1点也加入 ...
随机推荐
- section元素与div、article元素的区别
section元素是对网站或应用程序中页面上的内容进行分块,一个section元素通常有标题和内容组成.但section元素并非一个普通的容器元素,当一个容器需要直接定义样式或通过脚本定义行为时,推荐 ...
- java并发包分析之———Atomic类型
一.何谓Atomic? Atomic一词跟原子有点关系,后者曾被人认为是最小物质的单位.计算机中的Atomic是指不能分割成若干部分的意思.如果一段代码被认为是Atomic,则表示这段代码在执行过 ...
- 一篇文章带你了解Cloud Native
背景 Cloud Native表面看起来比较容易理解,但是细思好像又有些模糊不清:Cloud Native和Cloud关系是啥?它用来解决什么问题?它是一个新技术还是一个新的方法?什么样的APP符合“ ...
- Commandline OpenVPN client on Mac OSX with macports
http://www.tuicool.com/articles/FjuyQj 注:文中有些内容做了修改,特别是那个配置文件,不能直接抄着用. Most people use TunnelBrick ...
- Apache 流框架 Flink,Spark Streaming,Storm对比分析(一)
本文由 网易云发布. 1.Flink架构及特性分析 Flink是个相当早的项目,开始于2008年,但只在最近才得到注意.Flink是原生的流处理系统,提供high level的API.Flink也提 ...
- 下载Github上某个项目的子文件夹和单个文件
preface Github下的项目可能很大,里面有很多的子文件夹,我们可能只需要使用某个子目录下的资源,可以不用下载完整的repo就能使用. 例如,我想下载这个repo中的字典文件:https:// ...
- SEO优化:浅析关键词出现在网站哪些地方更有利?
关键词出现在网站哪些地方符合SEO?进行网站的SEO时,关键词需要出现在整个网站的适当地方.下面列出几个重要的关键词摆放的地方.以下列出的10个地方希望能够帮助到大家. 1.网站Title部分. 2. ...
- Python 3 利用 Dlib 19.7 实现摄像头人脸识别
0.引言 利用python开发,借助Dlib库捕获摄像头中的人脸,提取人脸特征,通过计算欧氏距离来和预存的人脸特征进行对比,达到人脸识别的目的: 可以自动从摄像头中抠取人脸图片存储到本地: 根据抠取的 ...
- 设计模式的征途—22.中介者(Mediator)模式
我们都用过QQ,它有两种聊天方式:一是私聊,二是群聊.使用QQ群,一个用户就可以向多个用户发送相同的信息和文件,从而无需一一发送,节省大量时间.通过引入群的机制,极大地减少系统中用户之间的两两通信,用 ...
- adSage :2013年教育行业搜索引擎投放分析报告