【学习笔记】神(奇)奇(技)思(淫)路(巧)
本篇是关于我做题时遇到的很神奇的技巧和思路的总结,会不定时更新
树上启发式合并
问题描述:
有一类问题是关于统计树上每个子树的某些信息,而且这些信息不支持高效合并,也就是说必须暴力整个子树才能得到答案。
方法总结:
最暴力的思路就是从根开始一棵一棵子树进行访问,访问完一棵子树清空一次我们统计的信息然后访问下一棵子树,子树全部搞完了就访问以当前点为根的树。我们会发现对于最后一棵子树的信息我们可以不清空,因为这棵子树的信息完全可以累加到我们的整棵树上,这样就可以少访问一棵子树,那么我们就考虑怎么做能让访问次数最少。很明显:让不清空信息的子树成为重儿子的那棵子树,即子树大小最大的那棵子树。
也就是:访问所有非重儿子的子树统计信息并清空,访问重儿子的子树然后将信息合并到整棵树上再访问整棵树的信息。
复杂度证明:
我们将连接重儿子的边称为重边,其他边称为轻边。那么对于节点 \(i\),他被访问的次数就是它到根上轻边的数量加一,而根到任何一个点的轻边数量不超过 \(\log n\) 条。因为对于轻边连接的儿子来说,每次它的子树大小至少会乘二,所以最多 \(\log n\) 次就结束了。
所以树上启发式合并的复杂度为: \(O(n \log n)\)
例题:
CF600E Lomsat gelral
代码:
点击查看代码
#include
using namespace std;
const long long MAXN = 1e5+5;
const long long MAXM = 2 * MAXN;
struct edge{
long long nxt,to;
edge(){}
edge(long long _nxt,long long _to){
nxt = _nxt,to = _to;
}
}e[MAXM];
long long tot,mx,sum,head[MAXN],sz[MAXN],son[MAXN],color[MAXN],cnt[MAXN],ans[MAXN];
void add_edge(long long from,long long to){
e[++tot] = edge(head[from],to);
head[from] = tot;
}
void get_son(long long now,long long fa){ //获取子树大小以及重儿子
sz[now] = 1;
long long maxn = 0;
for(long long i=head[now]; i; i = e[i].nxt){
long long to = e[i].to;
if(to == fa) continue;
get_son(to,now);
if(sz[to] > sz[maxn]){
maxn = to;
}
sz[now] = sz[now] + sz[to];
}
son[now] = maxn;
}
void get_ans(long long now,long long fa,long long heavy_son){ //暴力 dfs 当前子树的信息,除去重儿子
cnt[color[now]]++;
if(cnt[color[now]] > mx){
mx = cnt[color[now]];
sum = color[now];
}
else if(cnt[color[now]] == mx){
sum += color[now];
}
for(long long i=head[now]; i; i = e[i].nxt){
long long to = e[i].to;
if(to == fa || to == heavy_son) continue;
get_ans(to,now,heavy_son);
}
}
void clear(long long now,long long fa){ //清空当前子树的信息
cnt[color[now]]--;
for(long long i=head[now]; i;i = e[i].nxt){
long long to = e[i].to;
if(to == fa) continue;
clear(to,now);
}
}
void dfs(long long now,long long fa){ //启发式合并的过程
for(long long i=head[now]; i;i = e[i].nxt){
long long to = e[i].to;
if(to == fa || to == son[now]) continue;
dfs(to,now);
clear(to,now);
sum = mx = 0;
}
if(son[now]){ //重儿子不清空
dfs(son[now],now);
}
get_ans(now,fa,son[now]);
ans[now] = sum;
}
int main(){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
long long n;
cin>>n;
for(long long i=1; i<=n; i++){
cin>>color[i];
}
for(long long i=1; i>from>>to;
add_edge(from,to);
add_edge(to,from);
}
get_son(1,0);
dfs(1,0);
for(long long i=1; i<=n; i++){
printf("%lld ",ans[i]);
}
return 0;
}