一,树上启发式合并(DSU or GUNI)
- 离线算法,log复杂度优于树上莫队
- 启发式框架, s i z e size size 小的子树多来几遍,大的子树争取只要一遍
- 离线的框架是灵活的,统计答案的方法也是不同的
- From CF:方案起源
- 刷题记录
给出一棵 n 个节点以 1 为根的树,节点 u 的颜色为 c(u),现在对于每个结点 u 询问 u 子树里一共出现了多少种不同的颜色。 ( n < = 2 e 5 ) (n <= 2e5) (n<=2e5)
版本一:DFS序统计
要素
sz: 子树大小
son: 重儿子
c: 结点颜色
L[u]: 结点 u 的 DFS 序
R[u]: 结点 u 子树中结点的 DFS 序的最大值
rev[i]: DFS 序为 i 的结点
ans[i]: 存答案
cnt[i]: 颜色为 i 的结点个数
col_cnt: 目前出现过的颜色个数
基础树上DFS
- 获取重儿子
- 获取树上一个按照DFN递增的子树欧拉序
void dfs_tree(int x,int fa)
{
L[x]=++dfn_cnt;
rev[dfn_cnt]=x;
sz[x]=1;
for(int i=h[x];~i;i=nxt[i])
{
int j =e[i];
if(j==fa)continue;
dfs_tree(j,x);
sz[x]+=sz[j];
if(sz[j]>sz[son[x]])son[x]=j;
}
R[x]=dfn_cnt;
}
线性增删
void add_col(int x)
{
int pos = c[x];
if(cnt[pos]==0)col_cnt++;
cnt[pos]++;
}
void del_col(int x)
{
int pos = c[x];
cnt[pos]--;
if(cnt[pos]==0)col_cnt--;
}
DSU on tree
void dfs_dsu(int x,int fa,bool keep)
{
/// 计算轻儿子答案
for (int i=h[x];~i;i=nxt[i])
{
int j=e[i];
if(j==fa||j==son[x])continue;
dfs_dsu(j,x,false);
}
/// 计算重儿子答案,并保留
if(son[x]) dfs_dsu(son[x],x,true);
for (int i=h[x];~i;i=nxt[i])
{
int j=e[i];
if(j!=fa&&j!=son[x])
{
/// 子树结点的 DFS 序构成一段连续区间,可以直接遍历
rep(k,L[j],R[j])add_col(rev[k]);
}
}
/// 加入本身的颜色影响
add_col(x);
ans[x]=col_cnt;
/// 无关轻重全都要删,可以直接遍历
if(!keep)
{
rep(k,L[x],R[x])del_col(rev[k]);
}
}
版本二:树上递归统计
给定一个以 1 为根的 n 个结点的树,每个点上有一个字母(a-z),每个点的深度定义为该节点到 1 号结点路径上的点数。每次询问 a, b 查询以 a 为根的子树内深度为 b 的结点上的字母重新排列之后是否能构成回文串。
- 结论是简明的,只须统计出现次数为奇数的字母个数即可,但是统计是复杂的
- 线段树合并打咩!
基础树上DFS
void dfs_tree(int x,int fa)
{
dep[x]=dep[fa]+1;
sz[x]= 1;
for (auto j:g[x])
{
if(j==fa)continue;
dfs_tree(j,x);
sz[x]+=sz[j];
if(sz[hson[x]]<sz[j])hson[x]=j;
}
}
线性增删统计
void calc (int u,int f,int k)
{
统计整个子树的信息
cnt[dep[u]][color[u]]+=k;
for(auto v:g[u])
{
if(v!=f&&!vis[v])///不反祖且未被访问
{
calc(v,u,k);
}
}
}
void dfs_dsu(int u,int f,int keep)
{
for(auto v:g[u])
{
if(v!=hson[u]&&v!=f)
{
dfs_dsu(v,u,0);
}
}
/// 递归处理子树问题
if(hson[u])dfs_dsu(hson[u],u,1),vis[hson[u]]=1;
calc(u,f,1);///统计非重子树的信息
for(int i= 0;i < Q[u].size() ; i++)
{
int id = Q[u][i].first; ///询问编号
int d = Q[u][i].second; ///询问深度
int num = 0;
for(int j = 1;j<= 26;j++)
if(cnt[d][j]&1) num++;
ans[id]=num>1?0:1;
}
if(hson[u])vis[hson[u]]=0;
if(keep==0)calc(u,f,-1);
}