zoukankan      html  css  js  c++  java
  • Dsu on tree

    Dsu on tree

    简介

    Dsu on tree(树上启发式合并)是一种统计树上一个节点的子树中具有某种特征的节点数的算法 如CF 600E 。本质上是利用轻重链剖分对爆搜优化。

    如果一个问题具有如下性质:

    1.只有对子树的查询

    2.没有修改(静态)

    基本上就可以直接怼Dsu on tree了。


    算法讲解

    看问题实例:

    给出一棵树,每个节点有一种颜色。
    求每个节点以当前节点为根的子树中出现次数最多的颜色的编号和。

    (其实就是CF600E)

    首先想想如何爆搜:

    遍历每个节点,每个节点统计一遍颜色有多少个。完事之后再消除当前节点的贡献,继续递归。

    每个节点都要遍历一遍子树,时间复杂度为(O(n^2))

    显然不够优秀。

    那么我们来看看它都做了些什么无用功

    对于最后一次搜索,它的结果是不用清空的,因为它的答案可以用于父节点答案统计。

    那我们可以试着留住尽量大的子树,也就是重儿子,那么就树剖一遍求得重儿子,回溯时不擦除就行了。

    关于时间复杂度,因为每个点的轻边只有 (O(log n)) 条,故时间复杂度为 (O(nlog n))

    这里Orz一下发明人,把暴力玩到这么优雅( (

    核心代码模板:

    void dfs(int x,int f,int p)
    /*x:当前节点  f:当前节点父节点  p:当前节点是否需要保留*/
    {
    	for(/*遍历所有相邻节点*/)
    	{
    		int y=/*遍历到的节点*/
    		if(/*y不是重儿子或父节点*/) dfs(y,x,0);
    		
    	}
    	if(/*当前节点有重儿子*/) dfs(/*重儿子*/,x,1),Son=/*重儿子编号*/;	//统计重儿子,不消除影响 
    	
    	/*统计所有轻儿子的答案*/
    	
    	/*更新答案并删除贡献*/ 
    }
    

    CF600E code:

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int N=1e5+10;
    
    int head[N],ver[N<<1],nxt[N<<1],tot=0;
    void add(int x,int y)
    {
    	ver[++tot]=y;
    	nxt[tot]=head[x];
    	head[x]=tot;
    }
    
    int n;
    int col[N];
    int son[N],size_[N],cnt[N];
    int maxn,Son;
    ll sum=0,ans[N];
    
    void dfs1(int x,int f)//轻重链剖分 
    {
    	size_[x]=1;
    	for(int i=head[x];i;i=nxt[i])
    	{
    		int y=ver[i];
    		if(y==f) continue;
    		dfs1(y,x);
    		size_[x]+=size_[y];
    		if(size_[son[x]]<size_[y]) son[x]=y;
    	}
    }
    
    void add_(int x,int f,int val)//统计答案 
    /*val=1 统计答案   val=-1 删去答案*/
    {
    	cnt[col[x]]+=val;
    	if(cnt[col[x]]>maxn) maxn=cnt[col[x]],sum=col[x];
    	else if(cnt[col[x]]==maxn) sum+=col[x];
    	for(int i=head[x];i;i=nxt[i])
    	{
    		int y=ver[i];
    		if(y==f||y==Son) continue;//重儿子不管 
    		add_(y,x,val);
    	}
    }
    
    void dfs2(int x,int f,int p)/*p=0 需要消除影响*/
    {
    	for(int i=head[x];i;i=nxt[i])
    	{
    		int y=ver[i];
    		if(y==f) continue;
    		if(y!=son[x]) dfs2(y,x,0); 
    	}
    	if(son[x]) dfs2(son[x],x,1),Son=son[x];//统计重儿子,不消除影响
    	
    	add_(x,f,1),Son=0;//统计所有轻儿子的贡献
    	ans[x]=sum; //更新答案
    	
    	if(!p) add_(x,f,-1),sum=0,maxn=0;//删除贡献 
    }
    
    int main()
    {
    	scanf("%d",&n);
    	for(int i=1;i<=n;i++)
    		scanf("%d",col+i);
    	for(int i=1;i<n;i++)
    	{
    		int x,y;
    		scanf("%d%d",&x,&y);
    		add(x,y);
    		add(y,x);
    	}
    	dfs1(1,1);
    	dfs2(1,1,0);
    	for(int i=1;i<=n;i++)
    	{
    		printf("%lld ",ans[i]);
    	}
    	return 0;
    }
    

    参考文章

    [Tutorial] Sack (dsu on tree)

    「dsu on tree」学习笔记(优雅的暴力)

    dsu on tree入门

    DSU on tree——令人惊叹的想法

  • 相关阅读:
    springmvc&参数绑定&异常处理&Json数据交互&上传图片
    从零开始搭建一个vue.js的脚手架
    supervisor配置详解(转)
    supervisor 从安装到使用
    linux下如何查看某软件是否已安装
    linux yum命令详解
    Vue.js 和 MVVM 小细节
    PHP7的异常处理机制,set_error_handler和set_exception_handler方法介绍
    使用 acme.sh 签发续签 Let‘s Encrypt 证书 泛域名证书
    SSH原理与运用(一):远程登录
  • 原文地址:https://www.cnblogs.com/IzayoiMiku/p/13981179.html
Copyright © 2011-2022 走看看