树上数颜色
先考虑暴力怎么做
dfs搜索每个节点,对于当前节点,用迭代遍历其子树并更新答案,在回溯的时候将计数数组清空,时间复杂度为
注意到,对于每次准备回溯的时候,设当前根节点为
那么我们选择的
这是对的,定义重儿子为一个节点的所有儿子中子树大小最大的那个儿子(如果有多个就随便选一个),那么
如果不是很清楚上面的过程,见下面代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e5+10;
int n,m;
int ans[N];
int c[N];
int Cnt,End[N<<1],Next[N<<1],Last[N];
int cnt[N],sz[N],L[N],R[N],visitime,Node[N],big[N];
int totcol;
void add(int x,int y)
{
End[++Cnt]=y,Next[Cnt]=Last[x],Last[x]=Cnt;
}
void dfs0(int x,int fa)
{
L[x]=++visitime;//L表示dfs序的起点
Node[visitime]=x;
sz[x]=1;
for(int i=Last[x];i;i=Next[i])
{
int u=End[i];
if(u==fa) continue;
dfs0(u,x);
sz[x]+=sz[u];
if(!big[x]||sz[big[x]]<sz[u]) big[x]=u;
}
R[x]=visitime;//R表示子树dfs序的最大值,在dfs序中[L[x],R[x]]就是子树
}
void add(int u)
{
if(cnt[c[u]]==0) ++totcol;
cnt[c[u]]++;
}
void del(int u)
{
cnt[c[u]]--;
if(cnt[c[u]]==0) --totcol;
}
void dfs1(int x,int fa,bool keep)//keep表示是否清空计数数组
{
for(int i=Last[x];i;i=Next[i])
{
int u=End[i];
if(u==big[x]||u==fa) continue;
dfs1(u,x,0);
}
if(big[x]) dfs1(big[x],x,1);
for(int i=Last[x];i;i=Next[i])
{
int u=End[i];
if(u==big[x]||u==fa) continue;
for(int j=L[u];j<=R[u];j++) add(Node[j]);
}
add(x);
ans[x]=totcol;
if(!keep)
for(int i=L[x];i<=R[x];i++) del(Node[i]);
}
int main()
{
scanf("%d",&n);
for(int i=1;i<n;i++)
{
int u,v;
scanf("%d%d",&u,&v);
add(u,v),add(v,u);
}
for(int i=1;i<=n;i++)
scanf("%d",&c[i]);
dfs0(1,0);
dfs1(1,0,0);
scanf("%d",&m);
for(int i=1;i<=m;i++)
{
int u;
scanf("%d",&u);
printf("%d\n",ans[u]);
}
return 0;
}
时间复杂度为
证明:对于每个节点
定义轻/重边表示连接父节点与轻/重儿子的边,显然满足条件的
那么这个与启发式合并有什么关系呢?其实就是证明过程换一种理解方式。我们从
当然我们会发现这种题目跟线段树合并很像。其实用线段树合并也是可以的,只不过代码更难写而且空间复杂度更高。所以以后遇到这种题目可以从树上启发式合并和线段树合并两种角度考虑
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构