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——令人惊叹的想法

  • 相关阅读:
    【leetcode】1365. How Many Numbers Are Smaller Than the Current Number
    【leetcode】1363. Largest Multiple of Three
    【leetcode】1362. Closest Divisors
    【leetcode】1361. Validate Binary Tree Nodes
    【leetcode】1360. Number of Days Between Two Dates
    【leetcode】1359. Count All Valid Pickup and Delivery Options
    【leetcode】1357. Apply Discount Every n Orders
    【leetcode】1356. Sort Integers by The Number of 1 Bits
    ISE应用入门的一些问题
    DDR的型号问题
  • 原文地址:https://www.cnblogs.com/IzayoiMiku/p/13981179.html
Copyright © 2011-2022 走看看