【算法】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\)序转成序列问题的\ ...
随机推荐
- vue 截取字符串
let str = 'abcdef'; str = str.slice();//返回整个字符串 abcdef str = str.substring();//返回整个字符串 abcdef str = ...
- Xshell 5
Xshell 5安装步骤(带安装包):http://www.cnblogs.com/ysocean/p/7702243.html
- es报错org.frameworkset.elasticsearch.ElasticSearchException: {"error":{"root_cause":[{"type":"cluster_block_exception","reason":"blocked by: [FORBIDDEN/12/index read-only / allow delete (api)];"}],
今天es在保存数据的时候报错 org.frameworkset.elasticsearch.ElasticSearchException: {"error":{"root ...
- Eclipse的Errors in required projec(s)问题
在Eclipse中运行代码时出现Errors exist in required project(s)弹窗提示,但是当前类并无错误,点击Proceed当前类仍然可以运行 错误展示: Errors ex ...
- QuerySet的常用方法
QuerySet常用方法 使用 connection.queries 可以查看sql语句 filter 将满足条件的结果返回,返回值为QuerySet对象 exclude 将满足条件的结果过滤掉,返回 ...
- python中一些相似用法的区别:index()和find(),dict[]和get()
index和find在字符串中的区别: index()方法和find()方法相似,唯一的区别就是find方法不包含索引值会返回-1,而index()不包含索引值会抛出异常 同样的:获取字典dict ...
- SaltStack之return与job管理
目录 1. SaltStack组件之return 1.1 return流程 1.2 使用mysql作为return存储方式 2. job cache 2.1 job cache流程 2.2 job管理 ...
- onvif学习一:wsdl 和soap
来源:https://www.cnblogs.com/huanghongbo/p/5920123.html WSDL是用来描述WebService的,它用XML的格式描述了WebService有哪些方 ...
- 2017-12-08 违法数据筛选.sql
SELECT R. ID, R.LKBH, R.CDBH, R.FXBH, R.ZJBH, R.SBBH, R.CPHM, R.CPYSBH, R.CPYS, R.CSYSBH, R.CSYS, R. ...
- Flink流处理(一)- 状态流处理简介
1. Flink 简介 Flink 是一个分布式流处理器,提供直观且易于使用的API,以供实现有状态的流处理应用.它能够以fault-tolerant的方式高效地运行在大规模系统中. 流处理技术在当今 ...