• 虚树学习笔记(洛谷2495 消耗战)


    因为辣鸡csdn,导致之前快写好的博客没了
    QWQ悲伤逆流成河qwqqq
    首先虚树,这个东西,我感觉是一种思想,或者是方法,而并不是一个数据结构什么的。
    他主要是用来解决:给出一棵树,每次询问选择一些关键点,求一些信息。
    这些信息的特点是,许多未选择的点可以通过某种方式剔除而不影响最终结果。
    于是就有了建虚树这个算法。我们根据原树的信息重新建树,这棵树中要尽量少地包含非关键节点。 这棵树就叫做虚树。这棵虚树包含任意两个关键节点的LCA。
    通常这类题(O(nm))(T)飞,而(O(sum 询问点个数))是能跑得过的QWQ

    那我们应该怎么构造虚树呢QWQ我们把所有关键点按照dfs序排好序。
    然后,虚树要有一个根。这里一般直接把1号节点设为根。构建虚树的主要过程就是使用一个栈,维护从根开始的一条链。这条链上的所有点的dfs序一定是递增的。
    我们把关键点扫描一遍,一边维护这个栈一边连边构建虚树。

    具体来说:

    1.把根节点放入栈中2.把所有关键点点扫一遍。设当前的节点为(x),栈顶(链末端)的节点为(y)。求出(x)(y)(lca)

    此时有2种情况。

    1)(lca)(y)。此时(x)(y)子树内。我们就把(x)压入栈,相当于直接把(x)添加到这条链的末尾。

    2)(x,y)分立在(lca)的两个子树中。此时y这个子树中的所有关键点一定都被遍历过了。(原因:设有一关键点a在y子树中,没有被遍历过。则(dfn[y]<dfn[a]<dfn[x])。但是我们是把关键点按照dfs序来排的,a一定在x之前被扫)

    因此y子树内的所有关键点都已经被被加入虚树。接下来我们要把(y->lca)这一段的点加入虚树。我们设栈顶的节点为(y),栈顶的第二个节点为(z)

    重复以下操作:

    1.若(dfn[z]>dfn[lca])直接连边(z->y),然后把(y)出栈。

    2.若(dfn[z]=dfn[lca])这意味着(z)就是(lca)。直接连边(lca->y)。此时子树已构建完毕。

    3.若(dfn[z]<dfn[lca]),说明(lca)(y)(z)夹在中间。此时我们必须要把(lca)加入虚树,所以连边(lca->y),然后把(y)弹出栈,把lca入栈。然后子树构造完毕。

    最后把栈里面的元素搞一搞,每次(addedge(s[top-1],s[top]))即可感觉配合代码会比较好理解

    void solve()
    {
    // memset(point,0,sizeof(point));
        cnt=0;
        sort(a+1,a+1+k,cmp);
        top=1;
        sta[top]=1;
        for (int i=1;i<=k;i++)
        {
            int l = lca(sta[top],a[i]);
            if (l!=sta[top])
            {
                while (top>1)
                {
                    if (dfn[sta[top-1]]>dfn[l])
                    {
                        addedge(sta[top-1],sta[top],0);
                        top--;
                    }
                    else
                    {
                        if (dfn[sta[top-1]]==dfn[l])
                        {
                            addedge(sta[top-1],sta[top],0);
                            top--;
                            break;
                        }
                        else
                        {
                          addedge(l,sta[top],0);
                          sta[top]=l;
                          break;
                        }
                    }
                    
                }
            }
            if (sta[top]!=a[i]) sta[++top]=a[i];
        }
        while (top>1)
        {
            addedge(sta[top-1],sta[top],0);
            top--;
        }
    }
    

    那么剩下的主要就是(dp)部分了

    首先,我们定义(mn[x])表示在原树上(1到x)的路径上边权的最小值,(f[x])表示切割完(x)子树内所有关键点的最小花费。

    对于当前点(x),如果他是关键点,那么必须切割这个点到1的路经上的一条边,那么就是(mn[x]),否则(f[x]=min(mn[x],sum f[son])

    上代码

    int dp(int x,int flag)
    {
        
        int sum=0;
        for (int &i=point[x];i;i=nxt[i])
        {
            int p = to[i];
            sum=sum+dp(p,flag);
        }
        if (tag[x]==flag)
        {
            return mn[x];
        }
        return min(sum,mn[x]);
    }
    

    其中有一个要注意的地方就是虚树的时候,因为每次要重新建树,所以要清空(point)数组,而由于时间原因,又不能直接(memset),所以我们只能使用奇妙的手段!自杀式遍历通过取地址,不断修改(point)

    for (int &i=point[x];i;i=nxt[i])
    

    由于死机了三次QWQ
    所以暂时没有办法放整个题的代码

  • 相关阅读:
    BZOJ-4008: [HNOI2015]亚瑟王 (概率期望DP)
    BZOJ-4832: [Lydsy2017年4月月赛]抵制克苏恩 (概率期望DP)
    BZOJ-1415: [Noi2005]聪聪和可可 (期望DP)
    BZOJ2425 [HAOI2010]计数
    BZOJ2424 [HAOI2010]订货
    BZOJ2423 [HAOI2010]最长公共子序列
    BZOJ2299 [HAOI2011]向量
    BZOJ2298 [HAOI2011]problem a
    BZOJ1040 [ZJOI2008]骑士
    BZOJ一天提交(AC) 51纪念
  • 原文地址:https://www.cnblogs.com/yimmortal/p/10161498.html
Copyright © 2020-2023  润新知