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. 本地缓存和redis

    项目中的传统架构在服务启动时 读取数据库的大部分数据到本地内存,在看到redis的作用时发出疑问,到底有什么样的区别以及怎么选择呢,下面是别人的回答 使用本地缓存快还是使用redis缓存好? Redi ...

  2. PHPStorm配置xdebug(phpStudy/wamp)

    一.下载并配置XDebug wamp环境: 1.获取xdebug

  3. 关于UWB技术:DecaWave公司的DW1000芯片资料

    关于人在隧道工作时都需要准确的精确度确定精准的位置.DecaWave公司的DW1000芯片,对定位上的精确度更是再适合不过了.符合IEEE802.15.4-2011超宽带标准.按照数据手册上应该最小误 ...

  4. 【luogu P3953 逛公园】 题解

    题目链接:https://www.luogu.org/problemnew/show/P3953 题外话:感觉2017年神题好多..这还不是最神的一道,真在考场上我也就写个最短路计数暴力了.现在在大佬 ...

  5. 解决error possibly undefined macro AC_MSG_ERROR

    问题 出现如下缺少宏的问题 error: possibly undefined macro: AC_MSG_ERROR error: possibly undefined macro: AC_SUBS ...

  6. C# windows服务安装及卸载

    --C# windows服务安装及卸载   保存BAT文件  执行即可 @SET FrameworkDir=%WINDIR%\Microsoft.NET\Framework@SET Framework ...

  7. lwip BUG ,导致 系统 死机

    pcb->snd_queuelen >= pbuf_clen(next->p) sys_arch_assert: in ..\..\User\lwip\src\core\tcp_in ...

  8. 二叉查找树(二叉排序树)(C语言)

    #include<stdio.h> #include "fatal.h" struct TreeNode; typedef struct TreeNode *Posit ...

  9. spring cloud gateway 之限流篇

    转载请标明出处: https://www.fangzhipeng.com 本文出自方志朋的博客 在高并发的系统中,往往需要在系统中做限流,一方面是为了防止大量的请求使服务器过载,导致服务不可用,另一方 ...

  10. Vue聊天框默认滚动到底部

    功能场景 在开发中,我们总能遇到某些场景需要运用到聊天框,比如客服对话.如果你不是一名开发人员,可能你在使用QQ或者聊天工具的时候并没有注意到,当你发出一条消息的时候,窗体会默认滚动到最底部,让用户可 ...