zoukankan      html  css  js  c++  java
  • [探究] dsu on tree,一类树上离线问题的做法

    dsu on tree.

    ( m 0x01) 前言(&)技术分析

    (old{dsu~on~tree}),中文别称“树上启发式合并”(虽然我并不承认这种称谓),大概是一种优雅的暴力,并且跟(dsu)毫无关系。于是我打算叫他(old{Elegantly~Direct~Counting~on~Tree}),“优雅的树上暴力统计”。

    严格来说,(old {EDCT})解决的问题范围并不广泛:

    1、维护子树信息;

    2、不能带修改操作。

    但这仍然掩盖不住这种算法自带的有趣的气质。笔者认为,这种算法虽然是个暴力,但是其中的技术含量还是不低的,代码也不是那么的浅显易懂,算是一个比较考察应用能力的算法。

    然后来看技术分析。

    首先,假设我们有这样一个问题:

    给定一棵有根树树,每个点有一个信息。现在考虑求出每个点子树内的规定的有效信息数量。

    (n,qleq 5cdot1e5)

    一般而言这样的题是可以上莫队的,但是便于展开就开到了(500,000)

    考虑(n^2)的暴力,即对每个节点都扫一遍子树。很容易发现这样是浪费的,因为会算重。我们考虑怎么对这棵树进行划分才能高效计算。

    考虑一种合适的划分方案。结合轻重链剖里面的结论,可以知道,在轻重链剖后,一个点到根不会超过(log n)条轻边。所以如果对于每个点,假设我们只计算他对轻祖先的贡献,需要至多(log n)次就可以解决;同时我们考虑重儿子,每个点至多会被当成一次重儿子,所以假设我们只计算他对父亲的贡献,那么至多(1)次就可以解决。所以最后的复杂度是(O(nlog n))的。

    现在考虑实现层面,其实是一种分治的思想。我们考虑首先分治(u)的轻儿子并清除轻儿子的贡献,然后暴力计算重儿子,然后暴力计算一整棵子树的贡献。首先第一步中清除贡献是必要的,因为分治出来的几个子问题相互独立,所以必须要独立计算。之后是重儿子,由于重儿子至多有一个,所以可以直接计算而不会影响其他状态。最终再暴力一遍计算轻儿子的贡献。

    所以这样就解决了维护树上信息的问题,复杂度(nlog n)

    (0x02) 入门题目选整

    感觉大部分blog找的题目都很不清真233

    ( m Task1) ( m Cf600E) Lomsat gelral

    一句话题意/

    一棵树有n个结点,每个结点都是一种颜色,每个颜色有一个编号,求树中每个子树的最多的颜色编号的和。

    考虑套( m EDCT)的板子:

    void dfs(int u, int fa){
    	sz[u] = 1 ;
    	for (int k = head[u] ; k ; k = E[k].next){
    		if (to(k) == fa) continue ;
    		dfs(to(k), u), sz[u] += sz[to(k)] ;
    		if (sz[to(k)] > sz[son[u]]) son[u] = to(k) ;
    	}
    }
    void dfs(int u, int fa, int mk){
    	for (int k = head[u] ; k ; k = E[k].next){
    		if (to(k) == fa || to(k) == son[u]) continue ; 
    		dfs(to(k), u, 0) ; 
    	}
    	if (son[u]) dfs(son[u], u, 1), vis[son[u]] = 1 ;
    	calc(u, fa, 1) ; ans[u] = res ; if (son[u]) vis[son[u]] = 0 ;
    	if (!mk) calc(u, fa, -1), res = 0, max_cnt = 0 ;
    }
    

    然后就是最后的calc函数怎么写了。考虑我们最暴力的做法是什么?就是把每个颜色统计一遍。所以这么写就OK了:

    void calc(int u, int fa, int mk){
    	buc[clr[u]] += mk ; 
    	if (mk > 0 && buc[clr[u]] >= max_cnt){
    		if (buc[clr[u]] > max_cnt) 
    		    res = 0, max_cnt = 1ll * buc[clr[u]] ;
    		res += 1ll * clr[u] ; 
    	}
    	for (int k = head[u] ; k ; k = E[k].next){
    		if (to(k) == fa || vis[to(k)]) continue ; 
    		calc(to(k), u, mk) ; 
    	}
    }
    

    ( m Task2 ~Cf570D) Tree Requests

    一句话题意:

    给定一个以1为根的n个节点的树,每个点上有一个字母((a-z)),每个点的深度定义为该节点到1号节点路径上的点数.每次询问((a,b))查询以(a)为根的子树内深度为(b)的节点上的字母重新排列之后是否能构成回文串.

    这种应该就是比较裸的( m EDCT)。有一步转化需要学会构造,即我们令一个字符的权值(val(x)= ext{1<<(x-'a')}),那么对与一个串( m S),我们令(k= m{Xor}_{i=1}^nit val m( S[i])),那么重排之后可以构成回文串(Longleftrightarrow) (size(k)leq 1),其中(size( m S))指集合( m S)内的元素个数,也就是二进制表示中(1)的个数。所以也是,直接爆算就可以了。

    void calc(int u, int fa){
    	buc[dep[u]] ^= (1 << base[u]) ;
    	for (int k = head[u] ; k ; k = E[k].next)
    		if (to(k) != fa && !vis[to(k)]) calc(to(k), u) ;
    }
    int getl(int x){
    	int ret = 0 ; 
    	while (x) ret += (x & 1), x >>= 1 ;
    	return (bool)(ret <= 1) ;
    }
    void del(int u, int fa){
    	buc[dep[u]] = 0 ;
    	for (int k = head[u] ; k ; k = E[k].next)
    		if (to(k) != fa && !vis[to(k)]) del(to(k), u) ;
    }
    void dfs(int u, int fa, int mk){
    	for (int k = head[u] ; k ; k = E[k].next)
    		if (to(k) != fa && to(k) != son[u]) dfs(to(k), u, 0) ;
    	if (son[u]) dfs(son[u], u, 1), vis[son[u]] = 1 ;
    	calc(u, fa) ; 
    	for (int k = 0 ; k < qs[u].size() ; ++ k) 
    		ans[u].pb(getl(buc[qs[u][k]])) ; 
    	vis[son[u]] = 0 ; if (!mk) del(u, fa) ; 
    } 
    

    哦,漏说一点,这题需要边查询边记录询问的答案,所以需要把询问离线起来一起飞(?)。

  • 相关阅读:
    poj 2054 Color a Tree(贪婪)
    Restful 和 Jersey介绍(Web Service )
    Android异步载入全解析之使用多线程
    50个Android开发技巧(11 为文字加入特效)
    HDU 4424 Conquer a New Region 最大生成树
    Spring通过工厂创建实例的注意事项
    配置rhel 6.4(64位)安装使用syslog-ng 3.5
    虚拟机上网设置
    Java学习——何为JNDI
    Swift初体验 (一)
  • 原文地址:https://www.cnblogs.com/pks-t/p/11749399.html
Copyright © 2011-2022 走看看