启发式合并

启发式合并

什么是启发式合并

启发式合并是一种特殊的技巧,可以帮助我们在 O ( n l o g n ) O(nlogn) O(nlogn) 的复杂度内解决大多数的,无修改子树问题。

问题举例

Question

You are given a rooted tree with root in vertex 1. Each vertex is coloured in some colour.

Let’s call colour c dominating in the subtree of vertex v if there are no other colours that appear in the subtree of vertex v more times than colour c. So it’s possible that two or more colours will be dominating in the subtree of some vertex.

The subtree of vertex v is the vertex v and all other vertices that contains vertex v in each path to the root.

For each vertex v find the sum of all dominating colours in the subtree of vertex v.

Input

The first line contains integer n (1 ≤ n ≤ 105) — the number of vertices in the tree.

The second line contains n integers c i c_i ci (1 ≤  c i c_i ci ≤ n), c i c_i ci — the colour of the i-th vertex.

Each of the next n - 1 lines contains two integers x j , y j x_j,y_j xj,yj(1 ≤ x j , y j x_j,y_j xj,yj ≤ n) — the edge of the tree. The first vertex is the root of the tree.

Output

Print n integers — the sums of dominating colours for each vertex.

题目大意

给出一棵有根无向树,其节点染有一定的颜色,其中一棵子树中出现出现次数最多的颜色称为占据了这颗子树的颜色(如几种颜色出现的次数一样多,则同时为占据了子树的颜色),问以每个节点为根节点的子树的占据颜色的编号的和为多少

解题方法

Solution A

容易想到对每个节点向下遍历其子树,统计子树中各种颜色出现的次数,将出现次数最多的颜色记录,然后再删去统计数据,时间复杂度 O ( n 2 ) O(n^2) O(n2)

Solution B

对树进行轻重链剖分,在最多 O ( l o g ( n ) ) O(log(n)) O(log(n))的复杂度内求出所有重链的数据,而轻子树最多层数有 l o g ( n ) log(n) log(n)层,若每层对轻子树中的所有点进行暴力则有可以得到所有点的相应信息,复杂度上限为 O ( n l o g ( n ) ) O(nlog(n)) O(nlog(n))

AC代码

#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
const int size=1e5+5;
bool big[size];
int cnt[size];
int col[size];
long long ans[size];
int sz[size];
vector<int> g[size];
void dfs0(int u,int p)
{
	sz[u]=1;
	for(auto v:g[u])
	{
		if(v!=p) dfs0(v,u),sz[u]+=sz[v];
	}
}
void init()
{
	memset(big,0,sizeof(big));
	memset(cnt,0,sizeof(cnt));
	memset(sz,0,sizeof(sz));
}
int cntmax=0;long long sum=0;
void add(int v, int p, int x){
    cnt[ col[v] ] += x;
    if(x>0&&cnt[col[v]]>=cntmax)
    {
    	if(cnt[col[v]]>cntmax)
    	cntmax=cnt[col[v]],sum=0;
    	sum+=col[v];
    }
    for(auto u: g[v])
        if(u != p && !big[u])
            add(u, v, x);
}
void dfs(int v, int p, bool keep){
    int mx = -1, bigChild = -1;
    for(auto u : g[v])
       if(u != p && sz[u] > mx)
          mx = sz[u], bigChild = u;
    for(auto u : g[v])
        if(u != p && u != bigChild)
            dfs(u, v, 0);  
    if(bigChild != -1)
        dfs(bigChild, v, 1), big[bigChild] = 1;  
    add(v, p, 1);
    ans[v]=sum;
    if(bigChild != -1)
        big[bigChild] = 0;
    if(keep == 0)
        add(v, p, -1),cntmax=0,sum=0;
}
int main()
{
	int n;
	while(~scanf("%d",&n))
	{
		init();
		for(int i=1;i<=n;i++) g[i].clear();
		for(int i=1;i<=n;i++) scanf("%d",&col[i]);
		for(int i=1;i<n;i++)
		{
			int u,v;
			scanf("%d%d",&u,&v);
			g[u].push_back(v);
			g[v].push_back(u);
		}
		dfs0(1,0);
		dfs(1,0,0);
		for(int i=1;i<=n;i++)
		{
			printf("%lld",ans[i]);
			if(i!=n) printf(" ");
			else puts("");
		}
	}
}

模板

int cnt[maxn];
bool big[maxn];
void add(int v, int p, int x){
    cnt[ col[v] ] += x;
    for(auto u: g[v])
        if(u != p && !big[u])
            add(u, v, x)
}
void dfs(int v, int p, bool keep){
    int mx = -1, bigChild = -1;
    for(auto u : g[v])
       if(u != p && sz[u] > mx)
          mx = sz[u], bigChild = u;
    for(auto u : g[v])
        if(u != p && u != bigChild)
            dfs(u, v, 0);  // run a dfs on small childs and clear them from cnt
    if(bigChild != -1)
        dfs(bigChild, v, 1), big[bigChild] = 1;  // bigChild marked as big and not cleared from cnt
    add(v, p, 1);
    //now cnt[c] is the number of vertices in subtree of vertex v that has color c. You can answer the queries easily.
    if(bigChild != -1)
        big[bigChild] = 0;
    if(keep == 0)
        add(v, p, -1);
}

例题2

Question

You are given a rooted undirected tree consisting of nn vertices. Vertex 11 is the root.

Let’s denote a depth array of vertex xx as an infinite sequence [ d x , 0 , d x , 1 , d x , 2 . . . d_{x,0},d_{x,1},d_{x,2}... dx,0,dx,1,dx,2...], where d x , i d_{x,i} dx,iis the number of vertices y such that both conditions hold:

  • x is an ancestor of y;
  • the simple path from xx to y traverses exactly i edges.

The dominant index of a depth array of vertex xx (or, shortly, the dominant index of vertex xx) is an index jj such that:

  • for every k &lt; j k&lt;j k<j, d x , k &lt; d x , j d_{x,k}&lt;d_{x,j} dx,k<dx,j;
  • for every k &gt; j k&gt;j k>j, d x , k ≤ d x , j d_{x,k}\le d_{x,j} dx,kdx,j

For every vertex in the tree calculate its dominant index.

Input

The first line contains one integer n( 1 ≤ n ≤ 1 0 6 1\le n\le 10^6 1n106) — the number of vertices in a tree.

Then n−1lines follow, each containing two integers x and y ( 1 ≤ x , y ≤ n 1\le x,y\le n 1x,yn, x≠y). This line denotes an edge of the tree.

It is guaranteed that these edges form a tree.

Output

Output n numbers. i-th number should be equal to the dominant index of vertex i.

Solution

通过启发式合并,对整棵树的所有子树的所有的点的数量,对其中有最多点数的深度进行记录,最后得出答案

AC代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<vector>
using namespace std;
const int size=1e6+5;
int sz[size];
int cnt[size];
bool big[size];
int dep[size];
int ans[size];
vector<int> g[size];
void dfs0(int v,int p,int depth)
{
	sz[v]=1;
	dep[v]=depth;
	for(auto u:g[v])
	{
		if(u!=p) dfs0(u,v,depth+1),sz[v]+=sz[u];
	}
}
int cntmax=0;int deepest=0;
void add(int v,int p,int x)
{
	cnt[dep[v]]+=x;
	if(x>0&&cnt[dep[v]]>cntmax) deepest=dep[v],cntmax=cnt[dep[v]];
	if(x>0&&cnt[dep[v]]==cntmax&&deepest>dep[v]) deepest=dep[v]; 
	for(auto u:g[v])
	{
		if(u!=p&&!big[u])add(u,v,x);
	}
}
void dfs(int v,int p,int keep)
{
	int bigChild=-1;int mx=-1;
	for(auto u:g[v])
	{
		if(u!=p&&sz[u]>mx)
		mx=sz[u],bigChild=u;
	}
	for(auto u:g[v])
	{
		if(u!=p&&u!=bigChild)
		dfs(u,v,0);
	}
	if(bigChild!=-1)
	dfs(bigChild,v,1),big[bigChild]=1;
	
	add(v,p,1);
	ans[v]=deepest-dep[v];
	if(bigChild!=-1) big[bigChild]=0;
	if(!keep) deepest=0,add(v,p,-1),cntmax=0;
}
int main()
{
 	int n;
 	scanf("%d",&n);
 	for(int i=1;i<n;i++)
 	{
 		int u,v;
 		scanf("%d%d",&u,&v);
 		g[u].push_back(v);
 		g[v].push_back(u);
 	}
 	dfs0(1,0,1);
 	dfs(1,0,0);
 	for(int i=1;i<=n;i++)
 	{
 		printf("%d",ans[i]);
 		if(i!=n) printf(" ");
 		else puts("");
 	}
 }

例题三

Question

Bobo has a tree with n vertices numbered by 1,2,…,n and (n-1) edges. The i-th vertex has color c i, and the i-th edge connects vertices a i and b i.

Let C(x,y) denotes the set of colors in subtree rooted at vertex x deleting edge (x,y).

Bobo would like to know R_i which is the size of intersection of C(a i,b i) and C(b i,a i) for all 1≤i≤(n-1). (i.e. |C(a i,b i)∩C(b i,a i)|)

Input

The input contains at most 15 sets. For each set:

The first line contains an integer n (2≤n≤10 5).

The second line contains n integers c 1,c 2,…,c n (1≤c_i≤n).

The i-th of the last (n-1) lines contains 2 integers a i,b i (1≤a i,b i≤n).

Output

For each set, (n-1) integers R 1,R 2,…,R n-1.

Solution

先对整棵树求出所有颜色的数量,再随意选择一个点作为根节点,对此根节点的树通过启发式合并的方式进行预处理,处理出每个节点为根节点的子树的各种颜色的数量,其每新具有一种颜色就让un1+1代表这棵子树中出现了一种新的颜色,而子树中的每种颜色与整棵树的该种颜色数量相同时,则让un2+1代表除了这棵子树的剩余部分剩余的颜色种数-1

AC代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<vector>
using namespace std;
const int size=1e6+5;
int sz[size];
int cnt[size];
bool big[size];
int dep[size];
int ans[size];
vector<int> g[size];
void dfs0(int v,int p,int depth)
{
	sz[v]=1;
	dep[v]=depth;
	for(auto u:g[v])
	{
		if(u!=p) dfs0(u,v,depth+1),sz[v]+=sz[u];
	}
}
int cntmax=0;int deepest=0;
void add(int v,int p,int x)
{
	cnt[dep[v]]+=x;
	if(x>0&&cnt[dep[v]]>cntmax) deepest=dep[v],cntmax=cnt[dep[v]];
	if(x>0&&cnt[dep[v]]==cntmax&&deepest>dep[v]) deepest=dep[v]; 
	for(auto u:g[v])
	{
		if(u!=p&&!big[u])add(u,v,x);
	}
}
void dfs(int v,int p,int keep)
{
	int bigChild=-1;int mx=-1;
	for(auto u:g[v])
	{
		if(u!=p&&sz[u]>mx)
		mx=sz[u],bigChild=u;
	}
	for(auto u:g[v])
	{
		if(u!=p&&u!=bigChild)
		dfs(u,v,0);
	}
	if(bigChild!=-1)
	dfs(bigChild,v,1),big[bigChild]=1;
	
	add(v,p,1);
	ans[v]=deepest-dep[v];
	if(bigChild!=-1) big[bigChild]=0;
	if(!keep) deepest=0,add(v,p,-1),cntmax=0;
}
int main()
{
 	int n;
 	scanf("%d",&n);
 	for(int i=1;i<n;i++)
 	{
 		int u,v;
 		scanf("%d%d",&u,&v);
 		g[u].push_back(v);
 		g[v].push_back(u);
 	}
 	dfs0(1,0,1);
 	dfs(1,0,0);
 	for(int i=1;i<=n;i++)
 	{
 		printf("%d",ans[i]);
 		if(i!=n) printf(" ");
 		else puts("");
 	}
 }
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值