简介

对于一颗静态树,O(nlogn)时间内处理子树的统计问题。是一种优雅的暴力。

算法思想

很显然,朴素做法下,对于每颗子树对其进行统计的时间复杂度是平方级别的。考虑对树进行一个重链剖分。虽然都基于重链剖分,但不同于树剖,我们维护的不是树链。

对于每个节点,我们先处理其轻儿子所在子树,轻子树在处理完后消除其影响。然后处理重儿子所在子树,保留其贡献。然后再暴力跑该点的轻子树,统计该点子树的最终答案。如果该点子树是轻子树,则消除该子树的影响,否则保留。用代码描述的话,大概是这个流程:

void dfs(int u,int fa,int hvy)
{
for(v :G[u])//处理轻子树
{
if(v==f||v==son[u])
continue;
dfs(v,u,0);
}
if(son[u])//处理重子树
dfs(son[u],u,1);
calc(u,fa,1);//暴力统计轻子树对该点答案的贡献
ans[u]=res;
if(!hvy)
calc(u,fa,-1);//若点u所在子树是轻子树,则逆着原来统计的操作来消除其影响。
}

以上体现大概思想,但遇到具体题目可能有很多细节需要思考。

复杂度分析

这个可能不能很容易的明白其为何高效,如何达到O(nlogn)。因此我们考虑每个节点对时间复杂度的贡献。如果真的明白上述的算法流程,可以知道我们执行暴力统计的都是对轻边所连的子树,因此每个点被遍历到的次数与它往上到根的轻边数量有关。而任一点到根的路径上,轻边的数量不会超过logn。因此每个点最多被遍历logn次。这样想应该好理解很多。

举例

Lomsat gelral

这是一道比较经典的入门题,有兴趣的可以练手,感受一下算法的思想,再做下一题。在此不给出代码。

下面稍微讲一下D. Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths

感觉这道题还是挺难的,要考虑不少细节。

题意大概就是每条边有一个字符(a-v),求每颗子树下最长的一条简单路径,其上的字符可重组成回文串。显然就是要至多只有一个字符出现奇数次。

我们把每种字符看作二进制上的一个位,即2的幂。则满足条件的简单路径,其边权异或结果必须为0或2的幂。

因此用到dp和dsu on tree的思想。a[i]表示点i到根的路径异或值,dp[i]表示a[x]=i的点中,深度最大的x的深度。

对于一颗以u为根的子树,它的答案路径(该路径默认包含u,因此可能不是最终答案)可能是1.u到其子树中某点的简单路径;2.u的两颗不同子树中的两点间的路径。前者直接判断来更新答案;对于后者两颗子树间的情况,需要不断更新每个异或值下的最大深度,方便对于跑到的点可以知道此时与它满足条件的另一点的最大深度,从而得知路径长来更新答案。然后若该子树为重子树,则保留dp信息,否则重置。

附上代码

#include<bits/stdc++.h>
#define dd(x) cout<<#x<<" = "<<x<<" "
#define de(x) cout<<#x<<" = "<<x<<"\n"
#define sz(x) int(x.size())
#define All(x) x.begin(),x.end()
#define pb push_back
#define mp make_pair
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> P;
typedef priority_queue<int> BQ;
typedef priority_queue<int,vector<int>,greater<int> > SQ;
const int maxn=5e5+10,mod=1e9+7,INF=0x3f3f3f3f;
int a[maxn],dp[maxn*10],sz[maxn],d[maxn],son[maxn],ans[maxn];
vector<int> G[maxn];
void dfs1(int u,int fa)
{
sz[u]=1;
d[u]=d[fa]+1;
a[u]^=a[fa];
for (auto& v:G[u])
{
dfs1(v,u);
sz[u]+=sz[v];
if (sz[v]>sz[son[u]])
son[u]=v;
}
}
int mx;
bool check(int x,int y)
{
int t=x^y,cnt=0;
for (int i=0;i<='v'-'a';++i)
cnt+=(t>>i)&1;
return cnt<=1;
}
void cal(int rt,int u)
{
if (check(a[u],a[rt]))
mx=max(mx,d[u]-d[rt]);
mx=max(mx,dp[a[u]]+d[u]-2*d[rt]);
for (int i=0;i<='v'-'a';++i)
mx=max(mx,dp[a[u]^(1<<i)]+d[u]-2*d[rt]);
for (auto& v:G[u])
cal(rt,v);
}
void upd(int u,int ty)
{
if (ty)
dp[a[u]]=max(dp[a[u]],d[u]);
else
dp[a[u]]=-INF;
for (auto& v:G[u])
upd(v,ty);
}
void dfs2(int u,int hvy)
{
for (auto&v :G[u])
{
if (v==son[u])
continue;
dfs2(v,0);
}
if (son[u])
dfs2(son[u],1);
mx=0;
mx=max(mx,dp[a[u]]-d[u]);
for (int i=0;i<='v'-'a';++i)
mx=max(mx,dp[a[u]^(1<<i)]-d[u]);
for (auto& v:G[u])
{
if (v==son[u])
continue;
cal(u,v);
upd(v,1);
}
ans[u]=mx;
if (hvy)
dp[a[u]]=max(dp[a[u]],d[u]);
else
{
for (auto& v:G[u])
upd(v,0);
dp[a[u]]=-INF;
}
}
void solve(int u)
{
for (auto& v:G[u])
{
solve(v);
ans[u]=max(ans[u],ans[v]);
}
}
int main()
{
int n;
cin>>n;
char c[2];
for (int i=2;i<=n;++i)
{
int f;
scanf("%d%s",&f,c);
G[f].pb(i);
a[i]=1<<(c[0]-'a');
}
for (int i=1;i<maxn*10;++i)
dp[i]=-INF;
dfs1(1,0);
dfs2(1,1);
solve(1);
for (int i=1;i<=n;++i)
printf("%d ",ans[i]);
return 0;
}

dsu on tree(树上启发式合并)的更多相关文章

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

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

  2. dsu on tree (树上启发式合并) 详解

    一直都没出过算法详解,昨天心血来潮想写一篇,于是 dsu on tree 它来了 1.前置技能 1.链式前向星(vector 建图) 2.dfs 建树 3.剖分轻重链,轻重儿子 重儿子 一个结点的所有 ...

  3. dsu on tree[树上启发式合并学习笔记]

    dsu on tree 本质上是一个 启发式合并 复杂度 \(O(n\log n)\) 不支持修改 只能支持子树统计 不能支持链上统计- 先跑一遍树剖的dfs1 搞出来轻重儿子- 求每个节点的子树上有 ...

  4. 树上启发式合并(dsu on tree)学习笔记

    有丶难,学到自闭 参考的文章: zcysky:[学习笔记]dsu on tree Arpa:[Tutorial] Sack (dsu on tree) 先康一康模板题吧:CF 600E($Lomsat ...

  5. 【Luogu U41492】树上数颜色——树上启发式合并(dsu on tree)

    (这题在洛谷主站居然搜不到--还是在百度上偶然看到的) 题目描述 给一棵根为1的树,每次询问子树颜色种类数 输入输出格式 输入格式: 第一行一个整数n,表示树的结点数 接下来n-1行,每行一条边 接下 ...

  6. 神奇的树上启发式合并 (dsu on tree)

    参考资料 https://www.cnblogs.com/zhoushuyu/p/9069164.html https://www.cnblogs.com/candy99/p/dsuontree.ht ...

  7. CF741D Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths 树上启发式合并(DSU ON TREE)

    题目描述 一棵根为\(1\) 的树,每条边上有一个字符(\(a-v\)共\(22\)种). 一条简单路径被称为\(Dokhtar-kosh\)当且仅当路径上的字符经过重新排序后可以变成一个回文串. 求 ...

  8. 树上启发式合并(dsu on tree)

    树上启发式合并属于暴力的优化,复杂度O(nlogn) 主要解决的问题特点在于: 1.对于树上的某些信息进行查询 2.一般问题的解决不包含对树的修改,所有答案可以离线解决 算法思路:这类问题的特点在于父 ...

  9. hdu6191(树上启发式合并)

    hdu6191 题意 给你一棵带点权的树,每次查询 \(u\) 和 \(x\) ,求以 \(u\) 为根结点的子树上的结点与 \(x\) 异或后最大的结果. 分析 看到子树,直接上树上启发式合并,看到 ...

随机推荐

  1. jquery tablesorter 动态加载数据时,排序。过滤失效解决方案

    解决方案:重置更新: $("table").trigger("update"); 1 官方 ajax表格数据添加实例: $(document).ready(fu ...

  2. 【深度森林第三弹】周志华等提出梯度提升决策树再胜DNN

    [深度森林第三弹]周志华等提出梯度提升决策树再胜DNN   技术小能手 2018-06-04 14:39:46 浏览848 分布式 性能 神经网络   还记得周志华教授等人的“深度森林”论文吗?今天, ...

  3. element-ui组件dialog遇到form

    Vue.js似乎成了一种潮流. UI框架element-ui也跟着成了一种潮流,不过得承认,至少我个人还是非常认可的,element-ui做的是真不错. 用到element-ui,那么在dialog中 ...

  4. pat L2_004

    一棵二叉搜索树可被递归地定义为具有下列性质的二叉树:对于任一结点, 其左子树中所有结点的键值小于该结点的键值: 其右子树中所有结点的键值大于等于该结点的键值: 其左右子树都是二叉搜索树. 所谓二叉搜索 ...

  5. select in关键字查询匹配多个字段

    select id from table where (num,name) in ((num1,'name1'),(num2,'name2'))

  6. IOC+EF+Core项目搭建IOC注入及框架(二)

    配置ServiceCollection /// <summary> /// 表示IServiceCollection的扩展 /// </summary> public stat ...

  7. Nginx用法详解

    nginx作为一个高性能的web服务器,想必大家垂涎已久,蠢蠢欲动,想学习一番了吧,语法不多说,网上一大堆.下面博主就nginx的非常常用的几个功能做一些讲述和分析,学会了这几个功能,平常的开发和部署 ...

  8. 适配方案(五)适配的基础知识之设备像素比 dpr 与 1px 物理像素

    设备像素比 dpr 与 1px 物理像素 物理像素(physical pixel) 手机屏幕上显示的最小单元,该最小单元具有颜色及亮度的属性可供设置,iphone6.7.8 为:750 * 1334, ...

  9. javaIO——CharArrayReader & CharArrayWriter

    上一篇学习了StringReader和StringWriter,CharArrayReader和CharArrayWriter的实现跟他们很像,拥有的方法也基本一样.区别只是,后者两个的数据操作目标是 ...

  10. spark2.0的10个特性介绍

    1. Spark 2.0 ! 还记得我们的第七篇 Spark 博文里吗?里面我用三点来总结 spark dataframe 的好处: 当时是主要介绍 spark 里的 dataframe,今天是想总结 ...