zoukankan      html  css  js  c++  java
  • dsu on tree 学习笔记

    一、概述

    (dsu on tree)通常用于解决子树上的问题,要求无修改操作且允许离线。

    对于这样的问题,我们以前学过了像树上莫队、点分治等做法,但(dsu on tree)的复杂度远优于他们

    虽然(dsu on tree)复杂度优秀,但其实,它就是一个优雅的暴力:

    遇到子树问题,最暴力的做法毫无疑问就是暴力枚举子树上的所有点统计答案,实际上(dsu on tree)就是这样做的。

    只不过,(dsu on tree)有着优雅的思想:轻重链剖分,它借用对每个点轻儿子与重儿子贡献的分别处理,达到了(mathcal O(nlog(n)))的复杂度。

    二、实现

    • 将询问离线,记录在子树的根节点上
    • 遍历整棵树,对于节点(u),先计算它轻儿子的答案,计算后删除信息
    • 计算它重儿子的答案,不删除信息
    • 将重子树的信息合并到(u)
    • 暴力遍历(u)的轻子树,将轻子树的信息合并到(u)
    • 处理(u)处的询问
    • 根据(u)是否是重儿子选择是否删除(u)的信息

    这就是(dsu on tree)的思想了,大家可能不太理解,我们从一道例题来感受一下:

    三、例题

    CF600E

    题意:

    • 给定一棵(n)个节点的以(1)为根的树,每个节点都有一个颜色。

    • 如果一种颜色在以(x)为根的子树内出现次数最多,称其在以(x)为根的子树中占主导地位。显然,同一子树中可能有多种颜色占主导地位。

    • 你的任务是对于每一个(i in[1,n]),求出以(i) 为根的子树中,占主导地位的颜色的编号和。

    题解:

    这是一道经典的(dsu on tree)题目了

    考虑如何暴力做:显然可以维护(w[i])表示(i)这种颜色出现的次数,同时记录(ret)表示目前处理的节点中出现次数最多的颜色的编号和。

    当遍历到(u)时,首先我们枚举它的轻儿子递归下去,遍历轻儿子后,要删除轻子树节点对于(w)的影响

    接着遍历重子树,这次我们保留这些节点的贡献

    那么全局变量中已经保存了重子树的信息了,轻子树的信息我们直接暴力枚举,修改(w)(ret)

    遍历完后,该节点的答案就是(u)的答案,最后,删除该节点的贡献,也是暴力枚举它的子树中的所有节点并删去。

    代码如下:

    int hson[N],siz[N],w[N],mx,son;
    ll ret,ans[N];
    inline void dfs(int u,int f){
    	siz[u]=1;
    	for(int i=first[u];i;i=e[i].nxt){
    		int v=e[i].v;
    		if(v==f) continue;
    		dfs(v,u);
    		siz[u]+=siz[v];if(siz[v]>siz[hson[u]]) hson[u]=v;
    	}//轻重链剖分模板
    }
    inline void work(int u,int f,int tp){
    	w[col[u]]+=tp;//tp=1表示要增加这个节点的贡献,-1则是减去该节点的贡献
    	if(w[col[u]]>mx) mx=w[col[u]],ret=col[u];
    	else if(w[col[u]]==mx) ret+=col[u];//更新ret
    	for(int i=first[u];i;i=e[i].nxt){
    		int v=e[i].v;
    		if(v==f||v==son) continue;//son保存的是重儿子,不要遍历到重儿子去
    		work(v,u,tp);
    	}
    }
    inline void dsu(int u,int f,int tp){//tp=1表示不删除信息,tp=0表示要删除
    	for(int i=first[u];i;i=e[i].nxt){
    		int v=e[i].v;
    		if(v==f||v==hson[u]) continue;
    		dsu(v,u,0); //处理轻儿子,要删除信息
    	}
    	if(hson[u]) dsu(hson[u],u,1),son=hson[u];//遍历重儿子,不删除信息
    	work(u,f,1);//暴力遍历轻子树
        son=0;//接下来删除贡献是暴力遍历整个子树而不仅是轻子树了
    	ans[u]=ret;
    	if(!tp) work(u,f,-1),mx=0,ret=0;//直接删除所有节点的贡献
    }
    

    看起来十分暴力吧?它的复杂度其实确实是(mathcal O(nlog(n)))的,这里给出了粗略的证明:

    首先,根据轻重链剖分的性质,每一个点到根的路径上至多有(mathcal O(log(n)))条轻边。

    考虑一个点(u)在什么时候会被遍历到:

    (u)被一个祖先节点(x)统计当且仅当(u)(x)的轻子树上,也就是说(x-u)的这条链第一条边是轻边,那么唯一一条轻边对应唯一一个(x),所以至多被统计(mathcal O(log(n)))

    (u)被一个祖先节点遍历以删除贡献当且仅当(x)是一个轻儿子,在(u)的祖先中,轻儿子的数量不超过(mathcal O(log(n)))个,于是也只会被遍历(mathcal O(log(n)))

    综上,因为每个点的信息修改是(mathcal O(1))的,所以总复杂度是(mathcal O(nlog(n)))

    四、更多例题

  • 相关阅读:
    Java实现 LeetCode 30 串联所有单词的子串
    Java实现 LeetCode 29 两数相除
    Java实现 LeetCode 29 两数相除
    Java实现 LeetCode 29 两数相除
    Java实现 LeetCode 28 实现strStr()
    Java实现 LeetCode 28 实现strStr()
    Java实现 LeetCode 28 实现strStr()
    Java实现 LeetCode 27 移除元素
    Java实现 LeetCode 27 移除元素
    字符编码终极笔记:ASCII、Unicode、UTF-8、UTF-16、UCS、BOM、Endian
  • 原文地址:https://www.cnblogs.com/tqxboomzero/p/14255655.html
Copyright © 2011-2022 走看看