【学习笔记】dsu on tree
我也不知道为啥这要起这名,完完全全没看到并查集的影子啊……
实际上原理就是一个树上的启发式合并。
特点是可以在$O(nlogn)$的时间复杂度内完成对无修改的子树的统计,复杂度优于莫队算法。
局限性也很明显:1.不能支持修改 2.只能支持子树统计,不能链上统计。(链上统计你不能直接树剖吗?)
那么它是怎么实现的呢?首先有一个例子:
树上每个节点都有一个颜色(那么一定是蓝色),
求每个节点的子树上有多少颜色为k的节点。(每个节点的k不一定相同)
$O(n^2)$的算法非常好想,以每个点为起点dfs一下就没了。
当然也有不那么暴力的做法,dfs序一下再主席树或者莫队随便搞搞也行。
那么我们先看看暴力是怎么做的。
每次统计x节点前,暴力将x的子树的贡献加入,统计结束后,再暴力删除贡献,消除影响。
我们发现这有很多无用的删除操作,考虑优化?
那么我们怎么用dsu上树优雅的解决这个问题呢?我们想到了树链剖分(轻重链剖分)。
具体的做法是,我们先统计一个点的轻儿子,再把它的影响消除。再统计重儿子,此时不必消除影响。
为了完成统计,最后再统计一遍轻儿子。
可以这么考虑:只有dfs到轻边时,才会将轻边的子树中合并到上一级的重链,
树链剖分将一棵树分割成了不超过$logn$条重链。
每一个节点最多向上合并$logn$次,单次修改复杂度$O(1)$。
所以整体复杂度是$O(nlogn)$的。
所以大概的模版是这样的:
void dfs2(int u,int f,int k){
for(int i=head[u];i;i=G[i].next){
int v=G[i].v;if(v==f||v==wson[u])continue;
dfs2(v,u,);
}
if(wson[u])dfs(wson[u],u,),now=wson[u];
calc(u,f,);
now=;ans[u]=sum;
if(k==)calc(u,f,-),sumv=,maxv=;
}
下面是两道烂大街的例题:
1. Lomsat gelral(cf600E)
n个点的有根树,以1为根,每个点有一种颜色。我们称一种颜色占领了一个子树当且仅当没有其他颜色在这个子树中出现得比它多。求占领每个子树的所有颜色之和。
就是刚才的裸题啊。
#include<bits/stdc++.h>
#define N 700010
using namespace std;
struct Edge{int u,v,next;}G[*N];
typedef long long ll;
int n,c[N],val[N],size[N],wson[N],fa[N];
ll ans[N];
int head[*N],tot=;
void addedge(int u,int v){
G[++tot].u=u;G[tot].v=v;G[tot].next=head[u];head[u]=tot;
G[++tot].u=v;G[tot].v=u;G[tot].next=head[v];head[v]=tot;
}
void dfs1(int u,int f=){
size[u]=;
for(int i=head[u];i;i=G[i].next){
int v=G[i].v;if(v==f)continue;
if(v==f)continue;
dfs1(v,u);
size[u]+=size[v];
if(size[v]>size[wson[u]])wson[u]=v;
}
}
bool vis[N];int maxv=;ll sum=;
void change(int u,int f,int k){
c[val[u]]+=k;
if(k>&&c[val[u]]>=maxv){
if(c[val[u]]>maxv)sum=,maxv=c[val[u]];
sum+=val[u];
}
for(int i=head[u];i;i=G[i].next){
int v=G[i].v;if(v==f||vis[v])continue;
change(v,u,k);
}
}
void dfs2(int u,int f=,bool used=){
for(int i=head[u];i;i=G[i].next){
int v=G[i].v;if(v==f||v==wson[u])continue;
dfs2(v,u);
}
if(wson[u])dfs2(wson[u],u,),vis[wson[u]]=;
change(u,f,);ans[u]=sum;
if(wson[u])vis[wson[u]]=;
if(!used)change(u,f,-),maxv=sum=;
}
inline int read(){
int f=,x=;char ch;
do{ch=getchar();if(ch=='-')f=-;}while(ch<''||ch>'');
do{x=x*+ch-'';ch=getchar();}while(ch>=''&&ch<='');
return f*x;
}
int main(){
n=read();
for(int i=;i<=n;i++)val[i]=read();
for(int i=;i<n;i++){
int u=read(),v=read();
addedge(u,v);
}
dfs1();dfs2();
for(int i=;i<=n;i++)printf("%I64d ",ans[i]);
}
当然这题也有不这么做的做法,随便从cf上粘了一个,大家自行意会……
#include<bits/stdc++.h>
#define N 100005
using namespace std;
vector<int>a[N];map<int,int>S[N];
int F[N],id[N],c[N],n,i,x,y;
long long G[N],ans[N];
void work(int x,int y,int color){
if (y>F[x]) F[x]=y,G[x]=;
if (y==F[x]) G[x]+=color;
}
void Union(int &x,int y){
if (S[x].size()<S[y].size()) swap(x,y);
for (map<int,int>::iterator it=S[y].begin();it!=S[y].end();it++)
work(x,S[x][it->first]+=it->second,it->first);
}
void DFS(int x,int fa){
id[x]=x;S[x][c[x]]=;
F[x]=;G[x]=c[x];
for (int i=,y;i<a[x].size();i++)
if ((y=a[x][i])!=fa)
DFS(y,x),Union(id[x],id[y]);
ans[x]=G[id[x]];
}
int main(){
scanf("%d",&n);
for (i=;i<=n;i++)
scanf("%d",&c[i]);
for (i=;i<n;i++)
scanf("%d%d",&x,&y),
a[x].push_back(y),
a[y].push_back(x);
DFS(,);
for (i=;i<=n;i++)
printf("%I64d ",ans[i]);
}
例2: Arpa's letter-marked tree and Mehrdad's Dokhtar-kosh paths(CF741D)
这题也很显然,如果重排后能形成回文串,那么出现奇数次的字符应该少于2个(即最多1个)如果只有a~v的话考虑把每个字符当做一个二进制位,把一个点i到根的路径异或值记为s[i],那么我们就是要对于每个x在子树中找到a和b,使得s[a]^s[b]为0或2的次幂,且dep[a]+dep[b]-dep[lca]*2最大。
#include<bits/stdc++.h>
#define N 500005
using namespace std;
int size[N],head[*N],tot=,wson[N],s[N],f[*N],ans[N],d[N],a[N];
char c[N];
int maxv,n,inf;
struct Edge{int u,v,next;}G[*N];
void addedge(int u,int v){
G[++tot].u=u;G[tot].v=v;G[tot].next=head[u];head[u]=tot;
//G[++tot].u=v;G[tot].v=u;G[tot].next=head[v];head[v]=tot;
}
void dfs1(int u,int fa){
size[u]=;d[u]=d[fa]+;
if(u!=)s[u]=s[fa]^(<<a[u]);
for(int i=head[u];i;i=G[i].next){
int v=G[i].v;
dfs1(v,u);
size[u]+=size[v];if(size[v]>size[wson[u]])wson[u]=v;
}
}
void calc(int rt,int u){
int now=s[u];
maxv=max(maxv,f[now]+d[u]-*d[rt]);
if((s[u]^s[rt])==)maxv=max(maxv,d[u]-d[rt]);
for(int i=;i<;i++){
now=(<<i)^s[u];
maxv=max(maxv,f[now]+d[u]-*d[rt]);
if((s[u]^s[rt])==(<<i))maxv=max(maxv,d[u]-d[rt]);
}
for(int i=head[u];i;i=G[i].next){
int v=G[i].v;calc(rt,v);
}
}
void change(int u,int k){
if(k)f[s[u]]=max(f[s[u]],d[u]);
else f[s[u]]=inf;
for(int i=head[u];i;i=G[i].next)change(G[i].v,k);
}
void dfs2(int u,int k){
for(int i=head[u];i;i=G[i].next){
int v=G[i].v;if(v==wson[u])continue;
dfs2(v,);
}
if(wson[u])dfs2(wson[u],);
maxv=;int now=s[u];
maxv=max(maxv,f[now]-d[u]);
for(int i=;i<;i++){
now=(<<i)^s[u];
maxv=max(maxv,f[now]-d[u]);
}
for(int i=head[u];i;i=G[i].next){
int v=G[i].v;if(v==wson[u])continue;
calc(u,v);change(v,);
}
ans[u]=maxv;
if(!k){
for(int i=head[u];i;i=G[i].next)change(G[i].v,);
f[s[u]]=inf;
}else f[s[u]]=max(f[s[u]],d[u]);
}
void erase(int u){
for(int i=head[u];i;i=G[i].next){
int v=G[i].v;erase(v);
ans[u]=max(ans[u],ans[v]);
}
}
int main(){
scanf("%d",&n);
for(int i=;i<=n;i++){
int u;scanf("%d %c\n",&u,&c[i]);
addedge(u,i);a[i]=c[i]-'a';
}
dfs1(,);
memset(f,,sizeof(f));inf=f[];dfs2(,);
erase();
for (int i=;i<=n;++i)printf("%d%c",ans[i]," \n"[i==n]);
}
大概是这样。
参考:
http://blog.csdn.net/qq_35392050/article/details/64537364
http://www.cnblogs.com/zzqsblog/p/6146916.html
【学习笔记】dsu on tree的更多相关文章
- [学习笔记]Dsu On Tree
[dsu on tree][学习笔记] - Candy? - 博客园 题单: 也称:树上启发式合并 可以解决绝大部分不带修改的离线询问的子树查询问题 流程: 1.重链剖分找重儿子 2.sol:全局用桶 ...
- [学习笔记]dsu on a tree(如何远离线段树合并)
https://www.zybuluo.com/ysner/note/1318613 背景 这玩意来源于一种有局限性的算法. 有一种广为人知的,树上离线维护子树信息的做法. (可以参照luogu360 ...
- 决策树学习笔记(Decision Tree)
什么是决策树? 决策树是一种基本的分类与回归方法.其主要有点事模型具有可得性,分类速度快.学习时,利用训练数据,根据损失函数最小化原则建立决策树模型:预测时,对新数据,利用决策树模型进行分类. 决策树 ...
- [学习笔记] Uplift Decision Tree With KL Divergence
Uplift Decision Tree With KL Divergence Intro Uplift model 我没找到一个合适的翻译,这方法主要应用是,探究用户在给予一定激励之后的表现,也就是 ...
- 【学习笔记】K-D tree 区域查询时间复杂度简易证明
查询算法的流程 如果查询与当前结点的区域无交集,直接跳出. 如果查询将当前结点的区域包含,直接跳出并上传答案. 有交集但不包含,继续递归求解. K-D Tree 如何划分区域 可以借助下文图片理解. ...
- dsu on tree 树上启发式合并 学习笔记
近几天跟着dreagonm大佬学习了\(dsu\ on\ tree\),来总结一下: \(dsu\ on\ tree\),也就是树上启发式合并,是用来处理一类离线的树上询问问题(比如子树内的颜色种数) ...
- 树上启发式合并(dsu on tree)学习笔记
有丶难,学到自闭 参考的文章: zcysky:[学习笔记]dsu on tree Arpa:[Tutorial] Sack (dsu on tree) 先康一康模板题吧:CF 600E($Lomsat ...
- dsu on tree学习笔记
前言 一次模拟赛的\(T3\):传送门 只会\(O(n^2)\)的我就\(gg\)了,并且对于题解提供的\(\text{dsu on tree}\)的做法一脸懵逼. 看网上的其他大佬写的笔记,我自己画 ...
- [dsu on tree]【学习笔记】
十几天前看到zyf2000发过关于这个的题目的Blog, 今天终于去学习了一下 Codeforces原文链接 dsu on tree 简介 我也不清楚dsu是什么的英文缩写... 就像是树上的启发式合 ...
- dsu on tree 学习笔记
这是一个黑科技,考虑树链剖分后,每个点只会在轻重链之间转化\(log\)次. 考虑暴力是怎么写的,每次枚举一个点,再暴力把子树全部扫一边. \(dsu\ on\ tree.\)的思想就是保留重儿子不清 ...
随机推荐
- Halcon的编程语法与数据处理——第8讲
1.跟其他语言不完全一致的表达符号 赋值符号 := 引号 ' ' (一律是单引号) 求商求余 / % (一个整数除以另一个数,如何使商是实型的?即浮点型) 逻辑运算 and or ...
- 菜刀连接webshell
中国菜刀,一个非常好用而又强大的webshell,它可不是用来切菜的做饭的道具哦,是一款专业的网站管理软件,大小只有300多KB,真是小巧实用啊!不过被不法分子利用到,就是一个黑站的利器了.我记得以前 ...
- linux下一些常用系统命令
查看系统打开的文件数 lsof|wc -l 查看当前目录下的文件数 find -type f | wc -l 查看某个目录下的文件数,注意这里/home包括其所有子目录 find /home -typ ...
- 优秀UX设计师的八条黄金法则
与用户保持亲密 成为成功的UX设计师最重要的先决条件之一就是与用户保持紧密的联系,以发现和了解他们的需求和爱好.理想情况下你应该让自己完全地成为产品用户,因为只有这样你才能理解背后的动机.“这样的 ...
- MySQL 组合查询 concat
concat( pms_user.f_pu_name, '(' , pms_user.f_pu_realName,')') as userIds
- SEO方式之HTTPS 访问优化详解
SEO到底要不要做HTTPS?HTTPS对SEO的重要性 正方观点 1.HTTPS具有更好的加密性能,避免用户信息泄露: 2.HTTPS复杂的传输方式,降低网站被劫持的风险: 3.搜索引擎已经全面支持 ...
- 2018.10.18 bzoj4105: [Thu Summer Camp 2015]平方运算(线段树)
传送门 线段树妙题. 显然平方几次就会循环(打表证明不解释). 然后所有环长度的lcmlcmlcm不大于70. 因此维护一下当前区间中的节点是否全部在环上. 不是直接暴力到叶子节点修改. 否则整体打标 ...
- UVa 11039 Building designing (贪心+排序+模拟)
题意:给定n个非0绝对值不相同的数,让他们排成一列,符号交替但绝对值递增,求最长的序列长度. 析:我个去简单啊,也就是个水题.首先先把他们的绝对值按递增的顺序排序,然后呢,挨着扫一遍,只有符号不同才计 ...
- IP之ALTDDIO_in仿真
需要添加altera_mf库,才可以仿真. 上升沿输出,把前一个时钟的数据输出来. `timescale 1 ns/ 1 ns; module altddio_in_ip_tb; reg rst; r ...
- ios判断设备是iphone还是ipad
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launc ...