dsu on tree详解
这个算法还是挺人性化的,没有什么难度 就是可能看起来有点晕什么的。
大体 思想是 利用重链刨分来优化子树内部的查询。
考虑一个问题要对每个子树都要询问一次。我们暴力显然是\(n^2\)的。
考虑一下优化这个过程,我们发现儿子的信息可以给父亲用但是不能给兄弟或兄弟里的儿子用。
如果是最大最小值我们只能暴力来搞 但如果是出现次数什么的我们可以利用捅差分来解决这个事情。
考虑我们每次先暴力扫轻儿子然后 再做重儿子然后再把轻儿子的代价加上算当前节点的代价然后再把轻儿子的代价给删掉。
我们发现轻儿子被加上删掉两次 而重儿子只做一次并且保留。
可以发现这样做的复杂度很低 考虑一个点到根有logn条轻边所以这样最坏一个点被暴力来回扫logn次 统计自身答案的时候被扫了1次。
最终复杂度为nlogn 说起来很容易但其实代码还是存在一些细节的 要想好再写。
例题:CF600ELomsat gelral
const int MAXN=100010;
int n,len,mx;
int a[MAXN],cnt[MAXN],root[MAXN],sz[MAXN],son[MAXN];
int lin[MAXN],nex[MAXN<<1],ver[MAXN<<1];ll ans[MAXN],w;
inline void add(int x,int y)
{
ver[++len]=y;
nex[len]=lin[x];
lin[x]=len;
}
inline void dfs(int x,int father)
{
sz[x]=1;
for(int i=lin[x];i;i=nex[i])
{
int tn=ver[i];
if(tn==father)continue;
dfs(tn,x);
sz[x]+=sz[tn];
if(sz[son[x]]<sz[tn])son[x]=tn;
}
}
inline void update(int x,int father,int op,int target)
{
cnt[a[x]]+=op;
if(op>0&&cnt[a[x]]>=mx)
{
if(cnt[a[x]]==mx)w+=a[x];
else w=a[x],mx=cnt[a[x]];
}
for(int i=lin[x];i;i=nex[i])
{
int tn=ver[i];
if(tn==father||tn==target)continue;
update(tn,x,op,target);
}
}
inline void dfs(int x,int father,int op)
{
for(int i=lin[x];i;i=nex[i])
{
int tn=ver[i];
if(tn==father||tn==son[x])continue;
dfs(tn,x,0);//处理轻儿子的答案且清除
}
if(son[x])dfs(son[x],x,1);//处理重儿子的答案
update(x,father,1,son[x]);//把轻儿子的代价加入
ans[x]=w;//答案
if(!op)update(x,father,-1,0),w=0,mx=0;//当前是轻儿子所以删掉
}
int main()
{
freopen("1.in","r",stdin);
n=read();
for(int i=1;i<=n;++i)a[i]=read();
for(int i=1;i<n;++i)
{
int x,y;
x=read();y=read();
add(x,y);add(y,x);
}
dfs(1,0);
dfs(1,0,1);
for(int i=1;i<=n;++i)printf("%lld ",ans[i]);
return 0;
}
虽然这道题可以使用线段树合并来做但是那样对空间和时间的花销都是nlogn的 所以dsu on tree在空间上要优于线段树合并。
且 常数上也必然小于线段树合并。
我们只是关注与dsu on tree的思想 使用重链刨分来进行优化。
再来一道简单的题目来简单再看一下:CF208E Blood Cousins
求有多少个点和某个点的K级祖先相同。不难想到先求出K级祖先然后求出K级祖先子树内深度为x的点的个数。
关于K级祖先的求法:可以倍增+长链刨分优化实现O(1)但是仅对这道题就没什么必要了 询问和n同阶。
可以直接倍增搞,我们还有更快的方法:离线 我们dfs一个点然后加到栈中我们维护某条链上的点。
询问直接查栈中的从前往后第K个元素即可。
考虑第二问 求子树内深度为x的点的个数。显然 dsu on tree。
当然可以使用线段树合并,还有更快的方法:离线 开捅统计对于询问进行捅内外的差分。综上这道题被离线干成了O(n).
为了学习dsu on tree这里使用dsu on tree.
这道题 离线大法好 又得到了一个求树上K级祖先的方法 离线开栈。
const int MAXN=100010;
int n,len,mx,tot,top,m;
int a[MAXN],s[MAXN],cnt[MAXN],root[MAXN],sz[MAXN],son[MAXN];
int lin[MAXN],nex[MAXN],ver[MAXN],d[MAXN],ans[MAXN];
vector<pii>g[MAXN],w[MAXN];
inline void add(int x,int y)
{
ver[++len]=y;
nex[len]=lin[x];
lin[x]=len;
}
inline void dfs(int x,int father)
{
d[x]=d[father]+1;sz[x]=1;
s[++top]=x;
for(int i=0;i<g[x].size();++i)
{
int tn=g[x][i].F;
if(tn>d[father])continue;
int ss=s[top-tn];
w[ss].push_back(mk(d[x],g[x][i].S));
}
for(int i=lin[x];i;i=nex[i])
{
int tn=ver[i];
if(tn==father)continue;
dfs(tn,x);
sz[x]+=sz[tn];
if(sz[son[x]]<sz[tn])son[x]=tn;
}
--top;
}
inline void update(int x,int father,int op,int target)
{
cnt[d[x]]+=op;
for(int i=lin[x];i;i=nex[i])
{
int tn=ver[i];
if(tn==father||tn==target)continue;
update(tn,x,op,target);
}
}
inline void dfs(int x,int father,int op)
{
for(int i=lin[x];i;i=nex[i])
{
int tn=ver[i];
if(tn==father||tn==son[x])continue;
dfs(tn,x,0);
}
if(son[x])dfs(son[x],x,1);
update(x,father,1,son[x]);
for(int i=0;i<w[x].size();++i)
ans[w[x][i].S]+=cnt[w[x][i].F];
if(!op)update(x,father,-1,0);
}
int main()
{
freopen("1.in","r",stdin);
n=read();
for(int i=1;i<=n;++i)
{
int x=read();
if(!x)root[++tot]=i;
else add(x,i);
}
m=read();
for(int i=1;i<=m;++i)
{
int x,y;
x=read();y=read();
g[x].push_back(mk(y,i));
}
for(int i=1;i<=tot;++i)dfs(root[i],0);
for(int i=1;i<=tot;++i)dfs(root[i],0,0);//dsu on tree
rep(1,m,i)printf("%d ",ans[i]?ans[i]-1:0);
return 0;
}
dsu on tree详解的更多相关文章
- [CF1009F] Dominant Indices (+dsu on tree详解)
这道题用到了dsu(Disjoint Set Union) on tree,树上启发式合并. 先看了CF的官方英文题解,又看了看zwz大佬的题解,差不多理解了dsu on tree的算法. 但是时间复 ...
- 【算法】关于图论中的最小生成树(Minimum Spanning Tree)详解
本节纲要 什么是图(network) 什么是最小生成树 (minimum spanning tree) 最小生成树的算法 什么是图(network)? 这里的图当然不是我们日常说的图片或者地图.通常情 ...
- 二叉查找树(binary search tree)详解
二叉查找树(Binary Search Tree),也称二叉排序树(binary sorted tree),是指一棵空树或者具有下列性质的二叉树: 若任意节点的左子树不空,则左子树上所有结点的值均小于 ...
- BTree和B+Tree详解
https://www.cnblogs.com/vianzhang/p/7922426.html B+树索引是B+树在数据库中的一种实现,是最常见也是数据库中使用最为频繁的一种索引.B+树中的B代表平 ...
- ODT(old driver tree)详解(带例题)
文章目录 ODT简介 实现前提&&实现原理 初始化 split操作 assign操作 其它操作 区间第k小 区间加 区间所有数的k次方和 几道水题 ODT简介 ODT(old driv ...
- 数据结构31:树(Tree)详解
复制广义表数据结构中的树 树是数据结构中比较重要也是比较难理解的一类存储结构.本章主要主要围绕二叉树,对树的存储以及遍历做详细的介绍,同时还会涉及到有关树的实际应用,例如构建哈弗曼编码等. 由于树存储 ...
- 【2018.9.26】K-D Tree详解
网上对K-D-Tree的讲解不尽清晰,我学了很久都不会写,这里新开一文做一些讲解. 1.K-D-Tree是什么? K-DTree 即 K-Dimensional-Tree,常用来作空间划分及近邻搜索, ...
- dsu on tree (树上启发式合并) 详解
一直都没出过算法详解,昨天心血来潮想写一篇,于是 dsu on tree 它来了 1.前置技能 1.链式前向星(vector 建图) 2.dfs 建树 3.剖分轻重链,轻重儿子 重儿子 一个结点的所有 ...
- Ext.Net学习笔记22:Ext.Net Tree 用法详解
Ext.Net学习笔记22:Ext.Net Tree 用法详解 上面的图片是一个简单的树,使用Ext.Net来创建这样的树结构非常简单,代码如下: <ext:TreePanel runat=&q ...
随机推荐
- 转载--未看关于移动端Web远程开发调试
移动端Web开发调试之Chrome远程调试(Remote Debugging) http://blog.csdn.net/freshlover/article/details/42528643 移动端 ...
- elasticsearch 单节点搭建与爬坑记录
elasticsearch 单节点搭建与爬坑记录 prepare 虚拟机或者云服务器(这里用的是阿里云ECS) linux---centos7 安装完毕的jdk 相应的安装包(在https:/ ...
- 洛谷 P4042 [AHOI2014/JSOI2014]骑士游戏
题意 有\(n\)个怪物,可以消耗\(k\)的代价消灭一个怪物或者消耗\(s\)的代价将它变成另外一个或多个新的怪物,求消灭怪物$的最小代价 思路 \(DP\)+最短路 这几天做的第一道自己能\(yy ...
- java 和 c++ 的三目运算符的区别
转载请注明出处:http://www.cnblogs.com/liangyongrui/p/6348001.html 以前很少用java,就知道java和c++差不多. 今天就踩了一个坑. 不吐糟,直 ...
- MyBatis-Plus 用起来真的很舒服
一.MyBatis-Plus 1.简介 MyBatis-Plus 是一个 Mybatis 增强版工具,在 MyBatis 上扩充了其他功能没有改变其基本功能,为了简化开发提交效率而存在. 官网文档地址 ...
- day31 反射,内置方法,元类
目录 一.反射 1 什么是反射 2 如何实现反射 二.内置方法 1 什么是内置方法 2 为什么要用内置方法 3 如何使用内置方法 3.1 str 3.2 del 三.元类 1 什么是元类 2 clas ...
- java IO流 (九) Path、Paths、Files的使用
1.NIO的使用说明:>Java NIO (New IO,Non-Blocking IO)是从Java 1.4版本开始引入的一套新的IO API,可以替代标准的Java IO AP.>NI ...
- python 装饰器(六):装饰器实例(三)内置装饰器
内置的装饰器和普通的装饰器原理是一样的,只不过返回的不是函数,而是类对象,所以更难理解一些. @property 在了解这个装饰器前,你需要知道在不使用装饰器怎么写一个属性. def getx(sel ...
- JavaScript之setinterval的具体使用
关于setInterval在api文档中也有很详细的解释,比如下面那两个: setInterval() 方法可按照指定的周期(以毫秒计)来调用函数或计算表达式. setInterval() 方法会不停 ...
- 前端07 /jQuery初识
前端07 /jQuery初识 目录 前端07 /jQuery初识 1.jquery介绍 1.1 jquery的优势 1.2 jquery是什么? 1.3 jquery的导入 2.jQuery的使用 2 ...