树上问题进阶


一,树上启发式合并(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);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流苏贺风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值