【Educational Codeforces Round 2E】【STL-map 启发式合并 or 线段树动态开节点 】Lomsat gelral 一棵树每点一个颜色问每个节点子树的颜色众数之和

E. Lomsat gelral
time limit per test
2 seconds
memory limit per test
256 megabytes
input
standard input
output
standard output

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 ci (1 ≤ ci ≤ n), ci — the colour of the i-th vertex.

Each of the next n - 1 lines contains two integers xj, yj (1 ≤ 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.

Sample test(s)
input
4
1 2 3 4
1 2
2 3
2 4
output
10 9 3 4
input
15
1 2 3 1 2 3 3 1 1 3 2 2 1 2 3
1 2
1 3
1 4
1 14
1 15
2 5
2 6
2 7
3 8
3 9
3 10
4 11
4 12
4 13
output
6 5 4 3 2 3 3 1 1 3 2 2 1 2 3

//【Educational Codeforces Round 2E】【STL-map 启发式合并】Lomsat gelral 一棵树每点一个颜色问每个节点子树的颜色众数之和
#include<stdio.h>
#include<string.h>
#include<ctype.h>
#include<math.h>
#include<iostream>
#include<string>
#include<set>
#include<map>
#include<vector>
#include<queue>
#include<bitset>
#include<algorithm>
#include<time.h>
using namespace std;
void fre(){freopen("c://test//input.in","r",stdin);freopen("c://test//output.out","w",stdout);}
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define MP(x,y) make_pair(x,y)
#define ls o<<1
#define rs o<<1|1
typedef long long LL;
typedef unsigned long long UL;
typedef unsigned int UI;
template <class T> inline void gmax(T &a,T b){if(b>a)a=b;}
template <class T> inline void gmin(T &a,T b){if(b<a)a=b;}
const int N=1e5+10,M=0,Z=1e9+7,ms63=1061109567;
int n,x,y;
bool vis[N];
vector<int>a[N];
map<int,int>mop[N];
int num[N];
LL ans[N];
void dfs(int x)
{
	vis[x]=1;
	for(int i=a[x].size()-1;~i;--i)
	{
		int y=a[x][i];
		if(!vis[y])
		{
			dfs(y);
			if(mop[x].size()<mop[y].size())
			{
				swap(mop[x],mop[y]);
				num[x]=num[y];
				ans[x]=ans[y];
			}
			for(map<int,int>::iterator it=mop[y].begin();it!=mop[y].end();++it)
			{
				int val=it->first;
				mop[x][val]+=it->second;
				if(mop[x][val]>num[x])
				{
					num[x]=mop[x][val];
					ans[x]=val;
				}
				else if(mop[x][val]==num[x])ans[x]+=val;
			}
			mop[y].clear();
		}
	}
}
int main()
{
	while(~scanf("%d",&n))
	{
		mop[1].clear();
		for(int i=1;i<=n;++i)
		{
			a[i].clear();
			vis[i]=0;
			scanf("%d",&x);
			mop[i][x]=1;
			num[i]=1;
			ans[i]=x;
		}
		for(int i=1;i<n;++i)
		{
			scanf("%d%d",&x,&y);
			a[x].push_back(y);
			a[y].push_back(x);
		}
		dfs(1);
		for(int i=1;i<=n;++i)printf("%lld ",ans[i]);
		puts("");
	}
	return 0;
}
/*
【trick&&吐槽】
启发式合并,又学会了一个新姿势,真是辣爱丝啊~

【题意】
给你一棵树,树有n(1e5)个节点,每个节点有一个颜色c[],(1<=c[]<=n)。
然后让你对每一个节点,都输出一个值。表示这个节点的子树,为众数的所有颜色的和。

【类型】
启发式合并 map or 线段树

【分析】
这题教会了我 启发式合并。
为什么叫做"启发式"呢?
因为这个是紧扣我们的目的、根据我们的经验,所选择的行之有效的方法。而不是系统地固定地解决问题。

比较有代表性的,包括——
1,并查集的合并操作
2,A* 搜索

这道题就类似于并查集的合并操作,
我们通过map,记录每个节点的子树颜色种类和数量,然后暴力把子树向父节点上合并。
具体而言,我们每次合并的时候,是把节点数小的set向节点数多的set合并。
只要基于这个原则,每个节点经过"被合并"操作一次,所属的map节点数就要翻倍。
由此,每个节点被合并的次数必然不超过logn次。
于是,总的复杂度就是O(n * logn(均摊合并次数) * logn(map常数))。
于是这道题就可以这样在合理的复杂度内AC掉啦!

另外一份代码会提供线段树做法——

【时间复杂度&&优化】
O(n * logn(均摊合并次数) * logn(map常数))
ps:对于vector\set\map,每次做交换的时候,事实上只是交换的名称下标,时间很快哦~

*/


【Educational Codeforces Round 2E】【线段树-动态开节点O(nlogn)】Lomsat gelral 一棵树每点一个颜色问每个节点子树的颜色众数之和

#include<stdio.h>
#include<string.h>
#include<ctype.h>
#include<math.h>
#include<iostream>
#include<string>
#include<set>
#include<map>
#include<vector>
#include<queue>
#include<bitset>
#include<algorithm>
#include<time.h>
using namespace std;
void fre(){freopen("c://test//input.in","r",stdin);freopen("c://test//output.out","w",stdout);}
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define MP(x,y) make_pair(x,y)
typedef long long LL;
typedef unsigned long long UL;
typedef unsigned int UI;
template <class T> inline void gmax(T &a,T b){if(b>a)a=b;}
template <class T> inline void gmin(T &a,T b){if(b<a)a=b;}
const int N=1e5+10,M=2e6,Z=1e9+7,ms63=1061109567;
int n,x,y;
bool vis[N];
vector<int>a[N];
int root[N];
LL ans[N];
struct B
{
	int ls,rs;
	int num;
	LL sum;
}b[M];
int Col,id;
void build(int o,int l,int r)
{
	b[o].ls=b[o].rs=0;
	b[o].num=1;
	b[o].sum=Col;
	if(l==r)return;
	int mid=(l+r)>>1;
	if(Col<=mid)build(b[o].ls=id++,l,mid);
	else build(b[o].rs=id++,mid+1,r);
}
void pushup(int o)
{
	int ls=b[o].ls;
	int rs=b[o].rs;
	b[o].num=max(b[ls].num,b[rs].num);
	b[o].sum=0;
	if(b[ls].num==b[o].num)b[o].sum+=b[ls].sum;
	if(b[rs].num==b[o].num)b[o].sum+=b[rs].sum;
}
void merge(int x,int y,int l,int r)
{
	if(l==r)
	{
		b[x].num+=b[y].num;
		return;
	}
	int mid=(l+r)>>1;
	if(!b[x].ls)b[x].ls=b[y].ls;
	else if(b[y].ls)merge(b[x].ls,b[y].ls,l,mid);
	if(!b[x].rs)b[x].rs=b[y].rs;
	else if(b[y].rs)merge(b[x].rs,b[y].rs,mid+1,r);
	pushup(x);
}
void dfs(int x)
{
	vis[x]=1;
	for(int i=a[x].size()-1;~i;--i)
	{
		int y=a[x][i];
		if(!vis[y])
		{
			dfs(y);
			merge(root[x],root[y],1,n);
		}
	}
	ans[x]=b[root[x]].sum;
}
int main()
{
	while(~scanf("%d",&n))
	{
		id=1;
		for(int i=1;i<=n;++i)
		{
			a[i].clear();
			vis[i]=0;
			scanf("%d",&Col);
			build(root[i]=id++,1,n);
		}
		for(int i=1;i<n;++i)
		{
			scanf("%d%d",&x,&y);
			a[x].push_back(y);
			a[y].push_back(x);
		}
		dfs(1);
		for(int i=1;i<=n;++i)printf("%lld ",ans[i]);
		puts("");
	}
	return 0;
}
/*
【trick&&吐槽】
启发式合并,又学会了一个新姿势,真是辣爱丝啊~

【题意】
给你一棵树,树有n(1e5)个节点,每个节点有一个颜色c[],(1<=c[]<=n)。
然后让你对每一个节点,都输出一个值。表示这个节点的子树,为众数的所有颜色的和。

【类型】
启发式合并 map or 线段树

【分析】
这题教会了我 启发式合并。
为什么叫做"启发式"呢?
因为这个是紧扣我们的目的、根据我们的经验,所选择的行之有效的方法。而不是系统地固定地解决问题。

比较有代表性的,包括——
1,并查集的合并操作
2,A* 搜索

这道题就类似于并查集的合并操作,
我们通过map,记录每个节点的子树颜色种类和数量,然后暴力把子树向父节点上合并。
具体而言,我们每次合并的时候,是把节点数小的set向节点数多的set合并。
只要基于这个原则,每个节点经过"被合并"操作一次,所属的map节点数就要翻倍。
由此,每个节点被合并的次数必然不超过lgn次。
于是,总的复杂度就是O(n * lgn(均摊合并次数) * lgn(map常数))。
于是这道题就可以这样在合理的复杂度内AC掉啦!

这道题还存在线段树做法——
我们用一个线段树维护[l,r]范围内,颜色的最多出现次数以及这些出现次数颜色编号的和。
对于每个节点,我们都建一棵线段树。然而,只有有意义的节点,我们才在线段树中开辟空间。
换句话说,就是——初始情况下,每颗节点的线段树只含有log(n)个节点空间,刚好覆盖自己的颜色。

然后,当我们要合并某个节点到其父节点的时候。
我们会从上层到下层,依次遍历所有节点。
如果父节点没有,直接拼接上子节点。
如果子节点没有,不操作。
否则我们就迭代合并。

同样的道理,每次合并的时候,只有父子都有的节点,才会经历合并操作。
于是这也相当于启发式合并,在O(nlog(n))级别内解决整个问题。

要注意的一点是,线段树上的节点数可达2n,总共可能需要4e6的内存空间(也可能并达不到,但是比Onlogn略多)。
处理完以上所有,这道题就AC喽!

【时间复杂度&&优化】
O(n * lgn(均摊合并次数) * lgn(map常数))
ps:对于vector\set\map,每次做交换的时候,事实上只是交换的名称下标,时间很快哦~

*/


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值