我们凭感觉,对于某一个节点,它的子树中的颜色数目的计算,可以先获取该节点的重儿子的子树的颜色数目,然后再去暴力跑其他轻儿子的子树的颜色数目,然后对这些数目进行合并。 这样子是不是快的多呢?很神奇的,我们的时间复杂度从
O
(
n
2
)
O(n^2)
O(n2) 变到了
O
(
n
log
n
)
O(n\log n)
O(nlogn)
略证:某个节点的遍历次数等于该节点到根节点的边中轻边的数量。从深度为
d
−
1
d-1
d−1 的节点经过轻边走到深度为
d
d
d 的节点,子树大小至少
×
2
\times 2
×2,所以轻边个数为
O
(
log
n
)
O(\log n)
O(logn),所以得证。
⌈
D
s
u
o
n
T
r
e
e
⌋
\lceil Dsu\ on\ Tree\rfloor
⌈DsuonTree⌋
树上启发式合并,主要思想就是处理出每个节点的重儿子。接下来,我们保留重儿子的贡献,跑轻儿子的贡献,得到当前节点的贡献;然后,我们删掉轻儿子的贡献。 主要是三个函数:
d
f
s
1
(
)
dfs1()
dfs1() 跑轻重儿子,
d
f
s
2
(
)
dfs2()
dfs2() 跑节点遍历,
g
o
(
)
go()
go() 增/删贡献。
我们通过一些全局变量来模拟存储重儿子中的贡献值。 然后好像就没了…
【代码】
时间复杂度:
O
(
n
log
n
)
O(n\log n)
O(nlogn) 空间复杂度:
O
(
n
)
O(n)
O(n)
vector<int>V[MAX];int co[MAX];int sz[MAX],hson[MAX];/// 子树大小和重儿子编号。没有重儿子则编号为0voiddfs1(int x,int f){
sz[x]=1;for(auto it : V[x]){if(it == f)continue;dfs1(it,x);
sz[x]+= sz[it];if(sz[it]> sz[hson[x]])hson[x]= it;}}int res =0;/// 目前有多少种不同的颜色int cnt[MAX],ans[MAX];/// cnt[i] 表示颜色i的出现次数 ans[i] 表示结点编号i的答案voidgo(int x,int f,int dec){
cnt[co[x]]+= dec;/// 增 / 删贡献if(dec ==1&& cnt[co[x]]==1)res++;/// 多了一种颜色if(dec ==-1&& cnt[co[x]]==0)res--;/// 少了一种颜色for(auto it : V[x]){if(it == f)continue;go(it,x,dec);}}voiddfs2(int x,int f,bool del){for(auto it : V[x]){if(it == f || it == hson[x])continue;/// 遍历轻儿子dfs2(it,x,true);}if(hson[x])dfs2(hson[x],x,false);/// 遍历重儿子
cnt[co[x]]++;if(cnt[co[x]]==1)res++;for(auto it : V[x]){if(it == f || it == hson[x])continue;/// 增加轻儿子贡献go(it,x,1);}
ans[x]= res;if(del)go(x,f,-1);/// 删除轻儿子贡献}intmain(){int n;scanf("%d",&n);for(int i =1;i < n;++i){int ta,tb;scanf("%d%d",&ta,&tb);
V[ta].push_back(tb);
V[tb].push_back(ta);}for(int i =1;i <= n;++i)scanf("%d",&co[i]);dfs1(1,1);dfs2(1,1,false);int Q;scanf("%d",&Q);while(Q--){int t;scanf("%d",&t);printf("%d\n",ans[t]);}return0;}
【例二:
L
o
m
s
a
t
g
e
l
r
a
l
Lomsat\ gelral
Lomsatgelral】
【题意】 给定
n
n
n 个节点的树,每个节点的颜色为
c
i
c_i
ci
a
n
s
i
ans_i
ansi 表示编号
i
i
i 的子树中,所有出现次数最多的颜色的编号和。 求出
a
n
s
1
∼
a
n
s
n
ans_1\sim ans_n
ans1∼ansn
【范围】
1
≤
c
i
≤
n
≤
1
0
5
1\le c_i\le n\le 10^5
1≤ci≤n≤105
【思路】 和例题一差不多,仍然要计算出
c
n
t
[
c
o
l
o
r
]
cnt[color]
cnt[color] 不过我们还要存储
m
x
mx
mx,表示目前子树的颜色的最多出现次数是多少 然后去动态更新这个
m
x
mx
mx,同时也要更新出
r
e
s
res
res ,意义同
a
n
s
i
ans_i
ansi
【代码】 时间复杂度:
O
(
n
log
n
)
O(n\log n)
O(nlogn) 空间复杂度:
O
(
n
)
O(n)
O(n)
vector<int>V[MAX];int co[MAX];int sz[MAX],hson[MAX];voiddfs1(int x,int f){
sz[x]=1;for(auto it : V[x]){if(it == f)continue;dfs1(it,x);
sz[x]+= sz[it];if(sz[it]> sz[hson[x]])hson[x]= it;}}
ll mx =0,res =0;
ll cnt[MAX],ans[MAX];voidgo(int x,int f,int dec){
cnt[co[x]]+= dec;if(dec ==1){/// 增加贡献可能会增加 resif(cnt[co[x]]> mx)res = co[x],mx = cnt[co[x]];elseif(cnt[co[x]]== mx)res += co[x];}for(auto it : V[x]){if(it == f)continue;go(it,x,dec);}}voiddfs2(int x,int f,bool del){for(auto it : V[x]){if(it == f || it == hson[x])continue;dfs2(it,x,true);}if(hson[x])dfs2(hson[x],x,false);for(auto it : V[x]){if(it == f || it == hson[x])continue;go(it,x,1);}
cnt[co[x]]++;if(cnt[co[x]]> mx)res = co[x],mx = cnt[co[x]];/// 更新res mxelseif(cnt[co[x]]== mx)res += co[x];
ans[x]= res;if(del)go(x,f,-1),mx = res =0;/// 删除子树,mx = res = 0}intmain(){int n;scanf("%d",&n);for(int i =1;i <= n;++i)scanf("%d",&co[i]);for(int i =1;i < n;++i){int ta,tb;scanf("%d%d",&ta,&tb);
V[ta].push_back(tb);
V[tb].push_back(ta);}dfs1(1,1);dfs2(1,1,false);for(int i =1;i <= n;++i)printf("%lld ",ans[i]);return0;}