虚树学习笔记

虚树

一点理解

  1. 将关键点按dfs序排序后,所有关键点相邻关键点的lca合起来构成虚树(通常还要加上整棵树的)。
    虚树至多 2k 2 k 个点。体现在实现中就是每次都pop若干点,并有机会push2个点。

  2. stk[]中存的是从根到当前点的递归栈中目前选入虚树的点。
    stk中的点之间都未连边(因为事实上关系还未确定),pop掉一个点的同时把它的父边连上。

  3. 插入一个点时,计算这个点与上一个点的LCA,然后分类讨论:
    虚树

    1. 当前点是上一个的子节点,直接把自己push进去
    2. 否则根据top-1判断是否弹出top,直到出现第3类情况(top-1的深度 > > <script type="math/tex" id="MathJax-Element-42">></script>LCA的深度)
    3. a. 需要pop当前栈顶并push LCA,注意LCA是刚弹出的点的父亲
      b. LCA就是当前栈顶,通常不需要做什么
      然后把自己push进去
  4. 初始时,栈中包含{root, a[0]}(硬点a[0]),然后依次加入a[1]a[n-1]
    root前面还要添加保护节点stk[0] = 0,并设置dep[0] = 0, dep[root] = 1,避免在栈中只有一个root时过度弹栈。

代码实现模板

Initialization:dfs处理in[MAXN], dep[MAXN],查lca用的ST表以及adde时要用的一些信息

int a[MAXN], stk[MAXN] = {0,1};
inline bool cmp(int u, int v) {return in[u] < in[v];}

void rebuild(int k) {
    for (int i = 0; i < k; i++) scanf("%d",a+i);
    sort(a, a+k, cmp);
    int *top = stk + 2;
    *top = a[0];
    for (int i = 1; i < k; i++) {
        int f = lca(a[i],*top); //seems (*top == a[i-1])
        //if (f == *top) case 1
        while (dep[*(top-1)] >= dep[f]) //case 2: more to pop
            adde(*top, *(top-1)), top--;
        if (*top != f)  //case 3a: pop top & push f
            adde(*top, f), *top = f;    //f is ancestor of *top
        //else case 3b: usually nothing to do
        *++top = a[i];  //push itself
    }
    do adde(*top, *(top-1));
    while (--top > stk + 1);    //clear stack (don't forget!)
}

虚树上dp时,可以直接在栈中记录dp值,因为stk本身就是虚树上dfs的递归栈。
adde换成pushup即可。注意栈中的儿子还未向父亲pushup,弹栈时才pushup

val_t solve(int k) {
    for (int i = 0; i < k; i++) scanf("%d",a+i);
    sort(a, a+k, cmp);
    int top = 2;
    dp[1].init();   //don't forget reinitialize
    dp[2].init( stk[2] = a[0] );
    for (int i = 1; i < k; i++) {
        int f = lca(a[i],stk[top]); //seems (stk[top] == a[i-1])
        //if (f == stk[top]) case 1
        while (dep[stk[top-1]] >= dep[f])   //case 2: more to pop
            dp[top-1].pushup( dp[top].finish() ), top--;
        if (stk[top] != f)  //case 3a: pop top & push f
            dp[top].upgrade( stk[top] = f );
        //else case 3b: usually nothing to do
        stk[++top] = a[i];  //push itself
        dp[top].init(a[i]);
    }
    do dp[top-1].pushup( dp[top].finish() );
    while (--top > 1);  //clear stack (don't forget!)
    return dp[1].finish();
}

为了演示如何将树形dp改写为栈中dp,约定树形dp采用如下模板形式

class val_t;
class dp_t {    //demon class type
public:
    void init();    //non-key point
    void init(int u);   //key point
    val_t finish(); //self-process
    void pushup(val_t son); //pushup from val of son
    void upgrade(int fa);   //inherit & pushup to fa
} dp[MAXN];

void dp_t::upgrade(int fa) { //universal demon definition
    dp[fa].init();
    dp[fa].pushup( finish() );
    *this = dp[fa];
}

val_t dfs(int x) {
    if (key[x]) dp[x].init(x);
    else dp[x].init();
    for (int u : son[x])
        dp[x].pushup( dfs(u) );
    return dp[x].finish();
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值