//【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,每次做交换的时候,事实上只是交换的名称下标,时间很快哦~
*/