【算法】dsu on tree初探
dsu on tree的本质是树上的启发式合并,它利用启发式合并的思想,可以将O(N^2)的暴力优化成O(NlogN),用于不带修改的子树信息查询。
具体如何实现呢?对于一个节点,继承它重儿子的信息,轻儿子直接dfs统计,更新完本节点的答案后,再dfs一次清除轻儿子的信息,相当于一个启发式合并的过程,因为一次合并会使得被遍历的子树变大一倍,所以一棵子树最多遍历logn次,也就是一个点最多被遍历logn次,于是最劣复杂度为O(NlogN)。
dsu on tree的具体流程
dfs计算轻儿子的答案,(有时候也需要继承轻儿子的答案)
dfs计算重儿子的答案,继承重儿子的答案(有时候不需要)
dfs计算轻儿子的贡献
更新当前结点的答案
dfs删去轻儿子的贡献
模板代码:
void dfs1(int x)
{
size[x]=;
for(int i=last[x];i;i=e[i].pre)
dfs1(e[i].too),size[x]+=size[e[i].too],size[e[i].too]>size[son[x]]&&(son[x]=e[i].too);
}
void update(int x,int fa,int delta)
{
// 更新信息
for(int i=last[x];i;i=e[i].pre)
if(e[i].too!=fa&&e[i].too!=skip)update(e[i].too,x,delta);
}
void dfs2(int x,int fa,bool heavy)
{
for(int i=last[x];i;i=e[i].pre)
if(e[i].too!=fa&&e[i].too!=son[x])dfs2(e[i].too,x,);
if(son[x])dfs(son[x],x,),skip=son[x],ans[x]=ans[son[x]];//有时不需要继承
update(x,fa,);skip=;
ans[x]+=....//有时在update里更新答案
if(!heavy)update(x,fa,-);
}
例1 CF600E
题目大意:统计每一个子树里众数的和(可以有多个众数)
记录每种颜色的出现次数,然后就是模板题了...
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<algorithm>
#define ll long long
using namespace std;
const int maxn=,inf=1e9;
struct poi{int too,pre;}e[maxn];
int n,x,y,tot,zs,skip;
int last[maxn],col[maxn],cnt[maxn],son[maxn],size[maxn];
ll sum,ans[maxn];
inline void read(int &k)
{
int f=;k=;char c=getchar();
while(c<''||c>'')c=='-'&&(f=-),c=getchar();
while(c<=''&&c>='')k=k*+c-'',c=getchar();
k*=f;
}
inline void add(int x,int y){e[++tot].too=y;e[tot].pre=last[x];last[x]=tot;}
void dfs1(int x,int fa)
{
size[x]=;
for(int i=last[x],too;i;i=e[i].pre)if((too=e[i].too)!=fa)
dfs1(too,x),size[x]+=size[too],size[too]>size[son[x]]&&(son[x]=too);
}
void update(int x,int fa,int delta)
{
cnt[col[x]]+=delta;
if(delta>&&cnt[col[x]]>zs)zs=cnt[col[x]],sum=col[x];
else if(delta>&&cnt[col[x]]==zs)sum+=col[x];
for(int i=last[x];i;i=e[i].pre)
if(e[i].too!=fa&&e[i].too!=skip)
update(e[i].too,x,delta);
}
void dfs(int x,int fa,bool heavy)
{
for(int i=last[x];i;i=e[i].pre)
if(e[i].too!=fa&&e[i].too!=son[x])dfs(e[i].too,x,);
if(son[x])dfs(son[x],x,),skip=son[x];
update(x,fa,);ans[x]=sum;skip=;
if(!heavy)update(x,fa,-),zs=sum=;
}
int main()
{
read(n);
for(int i=;i<=n;i++)read(col[i]);
for(int i=;i<n;i++)read(x),read(y),add(x,y),add(y,x);
dfs1(,);dfs(,,);
for(int i=;i<=n;i++)printf("%lld ",ans[i]);
}
例2 CF741D
题目大意:每一条边有一个字符,求每一个子树里最长的路径使得路径上的字符经过重新排列可以成为回文串
这题有点像上场atcoder的D题...因为一个字符串经过重新排列可以成为回文串当且仅当每种颜色的出现次数为偶数或者只有一种颜色的出现次数为奇数,于是可以想到利用异或来判断。将每一种字符映射到二进制上的一位(1<<(ch-'a)),预处理出点到根节点的路径上的异或值,那么判断两个节点的路径上是否可以经过重新排列成为回文串就是st[x]^st[y]是不是0或者2的幂。
所以这题我们只需要记录状态为i的最深深度,然后就又是模板题了。
注意状态为i的如果没有要设为-inf,否则会GG (调了一天T T
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxn=,inf=1e9;
struct poi{int too,dis,pre;}e[maxn];
int n,x,y,tot,skip,sum;
int st[maxn],col[maxn],size[maxn],dep[maxn],son[maxn],mx[<<],last[maxn],ans[maxn];
char ch[];
inline void read(int &k)
{
int f=;k=;char c=getchar();
while(c<''||c>'')c=='-'&&(f=-),c=getchar();
while(c<=''&&c>='')k=k*+c-'',c=getchar();
k*=f;
}
inline void add(int x,int y,int z){e[++tot].too=y; e[tot].dis=z; e[tot].pre=last[x]; last[x]=tot;}
void dfs1(int x,int fa)
{
size[x]=;
for(int i=last[x],too;i;i=e[i].pre) if((too=e[i].too)!=fa)
{
dep[too]=dep[x]+;
st[too]=st[x]^(<<e[i].dis);
dfs1(too,x);
size[x]+=size[too];
if(size[too]>size[son[x]]) son[x]=too;
}
}
void Count(int x,int fa,int f)
{
sum=max(sum,mx[st[x]]+dep[x]-*dep[f]);
for(int i=;i<;i++) sum=max(sum, mx[st[x]^(<<i)]+dep[x]-*dep[f]);
if(x==f)return;
for(int i=last[x],too;i;i=e[i].pre)
if((too=e[i].too)!=fa && too!=skip) Count(too,x,f);
}
void update(int x,int fa,int delta)
{
if(delta>) mx[st[x]]=max(mx[st[x]],dep[x]);
else mx[st[x]]=-inf;
for(int i=last[x],too;i;i=e[i].pre)
if((too=e[i].too)!=fa && too!=skip) update(too,x,delta);
}
void dfs2(int x,int fa,bool heavy)
{
for(int i=last[x],too;i;i=e[i].pre)
if((too=e[i].too)!=fa && too!=son[x])
dfs2(too,x,), ans[x]=max(ans[x],ans[too]);
if(son[x]) dfs2(son[x],x,), skip=son[x], ans[x]=max(ans[x],ans[son[x]]);
sum=; Count(x,fa,x); mx[st[x]]=max(mx[st[x]],dep[x]);
for(int i=last[x],too;i;i=e[i].pre)
if((too=e[i].too)!=fa && too!=skip)
Count(too,x,x), update(too,x,);
ans[x]=max(ans[x],sum); skip=;
if(!heavy) update(x,fa,-);
}
int main()
{
read(n); for(int i=;i<(<<);i++) mx[i]=-inf;
for(int i=;i<=n;i++) read(x), scanf("%s",ch), y=ch[]-'a',add(x,i,y);
dfs1(,); dfs2(,,);
for(int i=;i<=n;i++) printf("%d ",ans[i]);
}
【算法】dsu on tree初探的更多相关文章
- [算法学习] dsu on tree
简介 dsu on tree跟dsu没有关系,但是dsu on tree借鉴了dsu的启发式合并的思想. 它是用来解决一类树上的询问问题,一般这种问题有以下特征: \(1.\)只有对子树的查询: \( ...
- [dsu on tree]【学习笔记】
十几天前看到zyf2000发过关于这个的题目的Blog, 今天终于去学习了一下 Codeforces原文链接 dsu on tree 简介 我也不清楚dsu是什么的英文缩写... 就像是树上的启发式合 ...
- dsu on tree入门
先瞎扯几句 说起来我跟这个算法好像还有很深的渊源呢qwq.当时在学业水平考试的考场上,题目都做完了不会做,于是开始xjb出题.突然我想到这么一个题 看起来好像很可做的样子,然而直到考试完我都只想出来一 ...
- [学习笔记]Dsu On Tree
[dsu on tree][学习笔记] - Candy? - 博客园 题单: 也称:树上启发式合并 可以解决绝大部分不带修改的离线询问的子树查询问题 流程: 1.重链剖分找重儿子 2.sol:全局用桶 ...
- dsu on tree题表
dsu on tree,又名树上启发式合并.重链剖分,是一类十分实用的trick,它常常可以作为一些正解的替代算法: 1.DFS序+线段树/主席树/线段树合并 2.对DFS序分块的树上莫队 3.长链剖 ...
- DSU on Tree浅谈
DSU on tree 在之前的一次比赛中,学长向我们讲了了这样一个神奇的思想:DSU on tree(树上启发式合并),看上去就非常厉害--但实际上是非常暴力的一种做法;不过暴力只是看上去暴力,它在 ...
- 【CodeForces】741 D. Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths(dsu on tree)
[题意]给定n个点的树,每条边有一个小写字母a~v,求每棵子树内的最长回文路径,回文路径定义为路径上所有字母存在一种排列为回文串.n<=5*10^5. [算法]dsu on tree [题解]这 ...
- 【CodeForces】600 E. Lomsat gelral (dsu on tree)
[题目]E. Lomsat gelral [题意]给定n个点的树,1为根,每个点有一种颜色ci,一种颜色占领一棵子树当且仅当子树内没有颜色的出现次数超过它,求n个答案——每棵子树的占领颜色的编号和Σc ...
- dsu on tree总结
dsu on tree 树上启发式合并.我并不知道为什么要叫做这个名字... 干什么的 可以在\(O(n\log n)\)的时间内完成对子树信息的询问,可横向对比把树按\(dfs\)序转成序列问题的\ ...
随机推荐
- (转) maven snapshot和release版本的区别
在使用maven过程中,我们在开发阶段经常性的会有很多公共库处于不稳定状态,随时需要修改并发布,可能一天就要发布一次,遇到bug时,甚至一 天要发布N次.我们知道,maven的依赖管理是基于版本管理的 ...
- spring中实现基于注解实现动态的接口限流防刷
本文将介绍在spring项目中自定义注解,借助redis实现接口的限流 自定义注解类 import java.lang.annotation.ElementType; import java.lang ...
- python vs java Threadpool
python 实现threadpool线程池管理: from concurrent.futures import ThreadPoolExecutor as te from concurrent.fu ...
- Chrome 提标 您的浏览器限制了第三方Cookie...解决方法
最近升级Chrome后会出现 您的浏览器限制了第三方Cookie,这将影响您正常登录,您可以更改浏览器的隐私设置,解除限制后重试. 解决方法: chrome://flags/ 把这句复制到浏览器回车 ...
- KindEditor 编辑器前台得使用规范
官方网址:http://www.kindsoft.net/下载网址:http://www.kindsoft.net/down.php 引入得脚本: <link href="~/Cont ...
- centos7修改xshell默认访问端口由22修改为62058
1.vim /etc/ssh/sshd_config 2.新加端口62058:Port 62058 3.重启sshd服务:systemctl restart sshd 4.将新加端口添加到防火墙并重启 ...
- python 数组array的一些操作
对一些特定大小的元素进行操作 1.将数组Arr中大于100的值都设定为100 Arr[Arr >100] = 100 利用array索引的内置 numpy.minimum(Arr, 100 ...
- 安装node.js后npm不可用
安装node.js后npm不可用 最近要用Vue做项目,依赖node.js,于是按官网下载安装node 下载地址:https://nodejs.org/en/download/ 网上也有很多教程这里就 ...
- MYSQL获取表空间大小
SELECT table_name AS "Table", round(((data_length + index_length) / 1024 / 1024), 2) as si ...
- 用fgets()和fputs()代替gets()和puts()
gets()和puts不安全,有些平台会报错,如pat. gets输入字符串时,不进行数组下标的检查,也就是说当你的数组长度是n时,输入超过该长度的字符串的时候,编译不会出错,但是运行的时候会出现数组 ...