题意:
给出一个有NNN个点,以111号点为根的有根树。每个点有一种颜色ci≤Nc_i\le Nci≤N。
以某个点为根的子树中,如果一种颜色出现的次数不比其它颜色少,称它是这个点的支配颜色。
点的支配颜色的和,是指,某个点的所有支配颜色的编号的和。
求这棵树上每个点的支配颜色的和。
N≤105。N\le10^5。N≤105。
简单地考虑:
可以暴力统计每个点,每种颜色的出现次数。Θ(N2)\Theta(N^2)Θ(N2)
这种做法显然会爆炸,也显然有很大的优化空间。
我们知道到某个点的AnsAnsAns等于它的所有儿子的AnsAnsAns的和,所以儿子的信息不能白白浪费。
dfsdfsdfs,然后返回的时候把子树的贡献加到父亲上?
虽然好像少了删除的步骤,但是实际上,
不仅爆了空间(存每个点的答案一共Θ(N2)\Theta(N^2)Θ(N2)),也爆了时间(扫一遍加贡献一共Θ(N2)\Theta(N^2)Θ(N2))。
既然没法完全保存儿子的信息,再加上只能存储Θ(N)\Theta(N)Θ(N)级别的信息(每个颜色出现次数)
那么顶多就把一个儿子的信息带上来,其它儿子只能暴力了。显然带上重儿子最优。
这样能够减少多少开销?
DFSDFSDFS。访问某一个点的时候,
我们先把它的轻儿子都逐个递归下去solvesolvesolve,然后再解决重儿子。
统计完一个轻儿子要把它的信息抛弃掉,再统计下一个
而统计完重儿子,这个点的所有儿子也都有答案了。
可以留下重儿子的信息,再暴力加上轻儿子的信息和正在访问的点本身的信息,得出这个点的答案。
怎么分析复杂度?很明显我们需要知道的就是某一个点会被暴力统计多少次
已经有了轻重儿子的概念了,干脆按照树链剖分的思路来分析
轻边(u,v)(u,v)(u,v)有siz[v]≤siz[u]2siz[v]\le \frac{siz[u]}{2}siz[v]≤2siz[u],
并且由此可得一条从根向下的路径经过的轻边不会超过log2Nlog_2Nlog2N条,
经过重链数量也不会超过轻边数量+1+1+1。
某个点到根的路径上有多少条轻边,这个点就会被统计几次。
复杂度是Θ(NlogN)\Theta(NlogN)Θ(NlogN)。
ps.ps.ps.暴力统计可以在dfsdfsdfs序上也可以直接在树上暴力统计,随意了
注意AnsAnsAns可能爆intintint
本题也可以搞树上启发式合并,每次把小的合并进大的里面;还可以用mapmapmap暴力。
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<cctype>
using namespace std;
#define LL long long
#define add_edge(a,b) nxt[++tot]=head[a],head[a]=tot,to[tot]=b
int N,tot=0,mx=0;
LL tmp=0;
int ci[100005]={},nxt[200005]={},head[200005]={},to[200005]={};
int siz[100005]={},son[100005]={};
int cnt[100005]={};
LL ans[100005]={};
bool vis[100005]={};
void pthDec(int x,int fa)
{
siz[x]=1;
for(int t=1,i=head[x];i;i=nxt[i])
{
if(to[i]==fa)continue;
pthDec(to[i],x); siz[x]+=siz[to[i]];
if(siz[to[i]]>t)t=siz[to[i]],son[x]=to[i];
}
}
void modify(int x,int fa,int delta)
{
cnt[ci[x]]+=delta;
if((delta==1)&&(cnt[ci[x]]>=mx))
{
if(cnt[ci[x]]>mx)tmp=0,mx=cnt[ci[x]];
tmp+=ci[x];
}
for(int i=head[x];i;i=nxt[i])
{
if((to[i]==fa)||vis[to[i]])continue;
modify(to[i],x,delta);
}
}
void dfs(int x,int fa,bool keep)
{
for(int i=head[x];i;i=nxt[i])
{
if(to[i]==fa||to[i]==son[x])continue;
dfs(to[i],x,0);
}
if(son[x])dfs(son[x],x,1),vis[son[x]]=1;
modify(x,fa,1),ans[x]=tmp;
if(son[x])vis[son[x]]=0;
if(!keep)modify(x,fa,-1),mx=tmp=0;
}
int main()
{
scanf("%d",&N);
for(int i=1;i<=N;++i)scanf("%d",&ci[i]);
for(int u,v,i=1;i<N;++i)
{
scanf("%d%d",&u,&v);
add_edge(u,v); add_edge(v,u);
}
pthDec(1,0);
dfs(1,0,0);
for(int i=1;i<=N;++i)printf("%I64d ",ans[i]);
return 0;
}
本文探讨了一种优化的树形DP算法,用于高效计算有根树中每个节点的支配颜色和。通过引入轻重儿子概念,避免了传统方法的高时间和空间复杂度,实现了Θ(NlogN)的时间复杂度。
466

被折叠的 条评论
为什么被折叠?



