dsu on tree

树上启发式合并。我并不知道为什么要叫做这个名字。。。

干什么的

可以在\(O(n\log n)\)的时间内完成对子树信息的询问,可横向对比把树按\(dfs\)序转成序列问题的\(O(n\sqrt n)\)莫队算法。

怎么实现

当\(dfs\)到一个点\(u\),执行以下操作:

1、递归处理所有轻儿子;

2、递归处理重儿子;

3、计算整棵子树的贡献(在第2步中重儿子的贡献得以保留,所以不需要重复计算);

4、若点\(u\)不是其父亲的重儿子,删除整棵子树的贡献。

看上去像是暴力?

复杂度分析

只有当存在一条轻边时,这条轻边所连接的子树才需要被重新计算一次贡献。那么一个点被重复计算的次数就等于这个点到根路径上经过的轻边条数。而根据轻重链剖分那套理论,一个点到根路径上的轻边不会超过\(\log n\)条,所以这个算法的复杂度为\(O(n\log n*t)\),其中\(t\)为一次计算贡献的复杂度。

题目

懒得分开写博客了qaq

CF600E Lomsat gelral

luogu

一棵树有\(n\)个结点,每个结点都有一种颜色,求每个子树中出现次数最多的颜色(们)的编号之和。

sol:维护\(num_i\)表示有多少种颜色出现了\(i\)次,\(sum_i\)表示这些颜色的编号和。显然计算贡献是\(O(1)\)的。

code

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int gi(){
int x=0,w=1;char ch=getchar();
while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
if (ch=='-') w=0,ch=getchar();
while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
#define ll long long
const int N = 1e5+5;
int n,a[N],to[N<<1],nxt[N<<1],head[N],cnt;
int sz[N],son[N],vis[N],tot[N],num[N],Max;ll sum[N],ans[N];
void link(int u,int v){
to[++cnt]=v;nxt[cnt]=head[u];head[u]=cnt;
}
void dfs1(int u,int f){
sz[u]=1;
for (int e=head[u];e;e=nxt[e])
if (to[e]!=f){
dfs1(to[e],u);sz[u]+=sz[to[e]];
if (sz[to[e]]>sz[son[u]]) son[u]=to[e];
}
}
void update(int u,int f,int val){
int &t=tot[a[u]];
--num[t];sum[t]-=a[u];
t+=val;
++num[t];sum[t]+=a[u];
if (val>0) Max=max(Max,t);
else if (!num[Max]) --Max;
for (int e=head[u];e;e=nxt[e])
if (to[e]!=f&&!vis[to[e]])
update(to[e],u,val);
}
void dfs(int u,int f,int keep){
for (int e=head[u];e;e=nxt[e])
if (to[e]!=f&&to[e]!=son[u])
dfs(to[e],u,0);
if (son[u]) dfs(son[u],u,1),vis[son[u]]=1;
update(u,f,1);
ans[u]=sum[Max];
vis[son[u]]=0;
if (!keep) update(u,f,-1);
}
int main(){
n=gi();
for (int i=1;i<=n;++i) a[i]=gi();
for (int i=1;i<n;++i){
int u=gi(),v=gi();
link(u,v);link(v,u);
}
dfs1(1,0);dfs(1,0,1);
for (int i=1;i<=n;++i) printf("%I64d ",ans[i]);
puts("");return 0;
}

CF570D Tree Requests

luogu

给你一棵\(n\)个节点的树,每个节点上有一个小写英文字母。每次询问\(v_i,h_i\),表示在\(v_i\)子树中所有深度为\(h_i\)的点上的字母拿出来重新组合能否形成一个回文串。

sol:一些字母重新组合后能够形成回文串当且仅当存在至多一个字母的出现次数为奇数。所以可以把每个小写英文字母当作是一个二进制位然后维护子树中每个深度的异或和即可。

code

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int gi(){
int x=0,w=1;char ch=getchar();
while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
if (ch=='-') w=0,ch=getchar();
while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
const int N = 5e5+5;
struct node{int h,nxt;}a[N];
int n,m,nxt[N],head[N],val[N],ft[N];char s[N];
int dep[N],sz[N],son[N],vis[N],sum[N],ans[N];
void add(int v,int h,int id){
a[id]=(node){h,ft[v]};ft[v]=id;
}
void dfs1(int u,int f){
dep[u]=dep[f]+1;sz[u]=1;
for (int v=head[u];v;v=nxt[v]){
dfs1(v,u),sz[u]+=sz[v];
if (sz[v]>sz[son[u]]) son[u]=v;
}
}
void update(int u){
sum[dep[u]]^=val[u];
for (int v=head[u];v;v=nxt[v])
if (!vis[v]) update(v);
}
bool cal(int x){
int res=0;
while (x) ++res,x^=x&-x;
return res<=1;
}
void dfs(int u,int f,int keep){
for (int v=head[u];v;v=nxt[v])
if (v!=son[u]) dfs(v,u,0);
if (son[u]) dfs(son[u],u,1),vis[son[u]]=1;
update(u);
for (int i=ft[u];i;i=a[i].nxt) ans[i]=cal(sum[a[i].h]);
vis[son[u]]=0;
if (!keep) update(u);
}
int main(){
n=gi();m=gi();
for (int i=2,f;i<=n;++i)
f=gi(),nxt[i]=head[f],head[f]=i;
scanf("%s",s+1);
for (int i=1;i<=n;++i) val[i]=1<<s[i]-'a';
for (int i=1,v,h;i<=m;++i) v=gi(),h=gi(),add(v,h,i);
dfs1(1,0);dfs(1,0,1);
for (int i=1;i<=m;++i) puts(ans[i]?"Yes":"No");
return 0;
}

CF208E Blood Cousins

luogu

给你一片森林,每次询问和一个点有相同的\(k\)次祖先的点有多少个。

sol:倍增跳到那个祖先上然后就是只需要知道该深度有多少个点就行了。

code

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int gi(){
int x=0,w=1;char ch=getchar();
while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
if (ch=='-') w=0,ch=getchar();
while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
const int N = 1e5+5;
int n,m,nxt[N],head[N],root[N],fa[18][N],ft[N];
int dep[N],sz[N],son[N],vis[N],tot[N],ans[N];
struct node{int d,nxt;}a[N];
void add(int x,int d,int id){
a[id]=(node){d,ft[x]};ft[x]=id;
}
void dfs1(int u,int f){
fa[0][u]=f;dep[u]=dep[f]+1;sz[u]=1;
for (int i=1;i<=17;++i) fa[i][u]=fa[i-1][fa[i-1][u]];
for (int v=head[u];v;v=nxt[v]){
dfs1(v,u);sz[u]+=sz[v];
if (sz[v]>sz[son[u]]) son[u]=v;
}
}
void update(int u,int val){
tot[dep[u]]+=val;
for (int v=head[u];v;v=nxt[v])
if (!vis[v]) update(v,val);
}
void dfs(int u,int keep){
for (int v=head[u];v;v=nxt[v])
if (v!=son[u]) dfs(v,0);
if (son[u]) dfs(son[u],1),vis[son[u]]=1;
update(u,1);
for (int i=ft[u];i;i=a[i].nxt) ans[i]=tot[a[i].d]-1;
vis[son[u]]=0;
if (!keep) update(u,-1);
}
int main(){
n=gi();
for (int i=1;i<=n;++i){
int f=gi();
if (!f) root[++root[0]]=i;
else nxt[i]=head[f],head[f]=i;
}
for (int i=1;i<=root[0];++i) dfs1(root[i],0);
m=gi();
for (int i=1;i<=m;++i){
int x=gi(),k=gi(),d=dep[x];
for (int j=0;j<=17;++j) if (k&(1<<j)) x=fa[j][x];
add(x,d,i);
}
for (int i=1;i<=root[0];++i) dfs(root[i],0);
for (int i=1;i<=m;++i) printf("%d ",ans[i]);
puts("");return 0;
}

dsu on tree总结的更多相关文章

  1. CF 741D. Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths [dsu on tree 类似点分治]

    D. Arpa's letter-marked tree and Mehrdad's Dokhtar-kosh paths CF741D 题意: 一棵有根树,边上有字母a~v,求每个子树中最长的边,满 ...

  2. CF 570D. Tree Requests [dsu on tree]

    传送门 题意: 一棵树,询问某棵子树指定深度的点能否构成回文 当然不用dsu on tree也可以做 dsu on tree的话,维护当前每一个深度每种字母出现次数和字母数,我直接用了二进制.... ...

  3. [dsu on tree]【学习笔记】

    十几天前看到zyf2000发过关于这个的题目的Blog, 今天终于去学习了一下 Codeforces原文链接 dsu on tree 简介 我也不清楚dsu是什么的英文缩写... 就像是树上的启发式合 ...

  4. CF 375D. Tree and Queries【莫队 | dsu on tree】

    题意: 一棵树,询问一个子树内出现次数$≥k$的颜色有几种 强制在线见上一道 用莫队不知道比分块高到哪里去了,超好写不用调7倍速度!!! 可以用分块维护出现次数这个权值,实现$O(1)-O(\sqrt ...

  5. dsu on tree 树上启发式合并 学习笔记

    近几天跟着dreagonm大佬学习了\(dsu\ on\ tree\),来总结一下: \(dsu\ on\ tree\),也就是树上启发式合并,是用来处理一类离线的树上询问问题(比如子树内的颜色种数) ...

  6. UOJ#266. 【清华集训2016】Alice和Bob又在玩游戏 博弈,DSU on Tree,Trie

    原文链接https://www.cnblogs.com/zhouzhendong/p/UOJ266.html 题解 首先我们可以直接暴力 $O(n^2)$ 用 sg 函数来算答案. 对于一个树就是枚举 ...

  7. dsu on tree入门

    先瞎扯几句 说起来我跟这个算法好像还有很深的渊源呢qwq.当时在学业水平考试的考场上,题目都做完了不会做,于是开始xjb出题.突然我想到这么一个题 看起来好像很可做的样子,然而直到考试完我都只想出来一 ...

  8. 洛谷P4482 [BJWC2018]Border 的四种求法 字符串,SAM,线段树合并,线段树,树链剖分,DSU on Tree

    原文链接https://www.cnblogs.com/zhouzhendong/p/LuoguP4482.html 题意 给定一个字符串 S,有 q 次询问,每次给定两个数 L,R ,求 S[L.. ...

  9. [Codeforces741D]Arpa's letter-marked tree and Mehrdad's Dokhtar-kosh paths——dsu on tree

    题目链接: Codeforces741D 题目大意:给出一棵树,根为$1$,每条边有一个$a-v$的小写字母,求每个点子树中的一条最长的简单路径使得这条路径上的边上的字母重排后是一个回文串. 显然如果 ...

  10. BZOJ.4182.Shopping(点分治/dsu on tree 树形依赖背包 多重背包 单调队列)

    BZOJ 题目的限制即:给定一棵树,只能任选一个连通块然后做背包,且每个点上的物品至少取一个.求花费为\(m\)时最大价值. 令\(f[i][j]\)表示在点\(i\),已用体积为\(j\)的最大价值 ...

随机推荐

  1. python内置模块(三)

    hashlib模块 通过一个函数,把任意长度的数据转换为一个长度固定的数据串(通常用16进制的字符串表示). Python2中使用hashlib: import hashlib m = hashlib ...

  2. jq局部打印插件jQuery.print.js(兼容IE8)

    /* @license * jQuery.print, version 1.5.1 * (c) Sathvik Ponangi, Doers' Guild * Licence: CC-By (http ...

  3. domain是什么

    一:domain表达式 domain表达式:通常用来筛选数据记录.它们使用特殊的语法,以便于Odoo ORM 将它们解析后生成对应的SQL WHERE数据库筛选语句. 二:domain的写法 doma ...

  4. python中动态导入模块

    当导入的模块不存在时,就会报ImportError错误,为了避免这种错误可以备选其他的模块或者希望优先使用某个模块或包,可以使用try...except...导入模块或包的方式. 例如: Python ...

  5. select和epoll

    C/S编程模型,对每一个客户端都要开辟一个新的线程,效率必定低下.普通select模型是开辟两个线程,一个用来监听客户端的连接,另一个用于处理客户端请求. fd_set set; FD_ZERO(&a ...

  6. 编译nginx错误:make[1]: *** [/pcre//Makefile] Error 127

    --with-pcre=DIR 是设置源码目录,而不是编译安装后的目录.

  7. oracle 子查询的几个种类

    1.where型子查询: select cat_id,good_id,good_name from goods where good_id in (selct max(good_id) from go ...

  8. 浅谈spj

    SPJ(special judge)是个好玩的东西,毕竟各类神奇的题目SPJ经常作为救火工具(比如说一不小心出成验证类的题目). 但SPJ是个坑,毕竟只让用个“testlib.h”,输入还特别奇怪.今 ...

  9. activemq的高级特性:消息的可靠性

    高级特性之消息的可靠性 可靠性分为:生产者.消费者.生产者必须让mq收到消息,消费者必须能够接收到消息并且消费成功,这就是消息的可靠性. 1:生产者可靠性 Session session = conn ...

  10. activemq的搭建、启动,简单demo

    一.搭建activeMQ 在官网下载window版本,直接解压就可以. 二.启动 在解压完的目录/bin/win64,双击击activemq.bat,运行完之后打开浏览器,输入http://127.0 ...