DP也要搞动态,动态DP与全局平衡二叉树详(lue)解

该来的还是来了,我是真的不想写这个狗博客,是真的烦。

参考文献

很详细:https://www.luogu.com.cn/blog/ShadowassIIXVIIIIV/solution-p4643
ZFY:抄袭文献

术语

\(u.lightson\)\(u\)的轻儿子。\(u.heavyson\)\(u\)的重儿子。

例题

例题
(#`O′)喂,普通都没讲,为什么直接加强了!!!

思路

这道题目我们一看特别容易蒙蔽,修改,但是当我们想到链分治后,一大坨的奇思妙想就来了。

看看普通的DP方程式。

\(f_{u,0}\)为不在独立集中的最大值,那么\(f_{u,1}\)就是在的最大值,\(a_{u}\)\(u\)点权值。

那么就有DP方程式:

\(f_{u,0}=\sum\limits_{v∈u.son}max(f_{v,1},dp_{v,0})\)
\(f_{u,1}=\sum\limits_{v∈u.son}f_{v,0}+a_{u}\)

而这个搜索的顺序是无关紧要的,所以我们可以优先重链,然后轻链。

也就是先DFS重链并且记录所有轻儿子,然后再按顺序DFS轻儿子。

所以我们可以把树抽象成这样:

在这里插入图片描述
然后我们对于每个点,我们记录一个\(lf\)值:
\(lf_{u,0}=\sum\limits_{v∈u.lightson}max(f_{v,0},f_{v,1})\)
\(lf_{u,1}=\sum\limits_{v∈u.lightson}f_{v,0}\)

然后我们就可以单独对于重链跑DP了,成功把树上转换成链上,DP也就是:

\(f_{u,0}=lf_{u,0}+max(f_{u.heavyson,0},f_{u.heavyson,1})\)
\(f_{u,1}=lf_{u,1}+f_{u.heavyson,0}+a_{u}\)

然后我们就可以用链分治(树链剖分)了,然后用线段树。

等会等会,怎么用线段树?

考虑一些\(DP\)的本质,有一些DP其实就是一个数组乘上一个矩阵然后变成了DP后的数组,只不过有时候有优化罢了。

这个也是一样,不过矩阵乘法的形式跟以前不一样,是:
\(c_{i,j}=max_{k}(A_{i,k}+B_{k,j})\)

可以证明,这个矩阵乘法的形式依旧满足结合律,这就够了,单位矩阵为:\((0,0)\)\((n,n)\)全是\(0\),其他全是\(-inf\)

那么我们把矩阵写出来!!!\((v=u.heavyson)\)

\(\begin{bmatrix} f_{v,0} & f_{v,1} \end{bmatrix}\times\begin{bmatrix} lf_{u,0}&lf_{u,1}+a_{u} \\ lf_{u,0} &-inf \end{bmatrix}\)

为了方便,下文的\(lf_{u,1}+=a_{u}\)

用树链剖分维护的话,现在就是\(O(n\log^2{n})\)(线段树维护后面的那个矩阵)。

全局平衡二叉树


以下摘自参考文献。

可以去翻07年的论文”QTREE 解法的一些研究",(“全局平衡二叉树"这个中二的名字是论文里说的)

众所周知把刚才的树剖换成lct就可以做到一个log了,但是我们发现lct实在是常数太!大!了!绝对是跑不过实现的优秀的一点的树剖的

但是我们对于lct的复杂度证明却很感兴趣,为啥同样是操作了\(logn\)个数据结构,把线段树换成常数更大的splay复杂度反而少了一个\(log\)呢?(刚才这句话严格来讲是病句,常数和复杂度没有任何关联)

具体证明需要用到势能分析,但是感性理解一下就是如果我们把lct上的虚边也看成splay的边的话,我们发现整棵lct变成了一棵大splay,只是有些点度数不是2了

但是这些点度不是2的点并未破坏splay的势能分析换句话说势能分析对整颗大splay仍然生效,所以你的\(log\)次splay在整个大splay上只是一次splay而已

复杂度自然是均摊\(O(\log{n})\)

但是,我们发现这是颗静态树,使用splay实在是大(常)材(数)小(过)用(大)了

于是我们考虑将lct强行静态化,换句话说,建一个像splay一样的全局平衡的树

观察到线段树只是局部平衡的,在碰到专业卡链剖的数据--链式堆(根号n个长度为根号n的链连成完全二叉树的形状)的时候会导致算上虚边之后的整颗树左倾或者右倾

此时我们发现如果在建线段树的时候做点手脚,我们把线段树换成二叉查找树bst,并且这个bst不是严格平衡的话,我们可以做到更加优秀的复杂度,使得算上虚边之后的树树高达到\(O(log{n})\)级别

我们还是在树上dfs,但是对于重链建bst的时候我们并不建一个完美的bst,而是将每一个节点附上一个权值,权值为它所有轻儿子的siz之和+1,然后我们每次找这个链的带权重心,把他作为这一级的父亲,然后递归两边进行建bst

当然我们发现最坏情况下我们可以建出一个严重左倾或者右倾的bst

但是,我们考虑算上虚边的整颗树我们会发现一个神奇的性质,无论是经过一条重的二叉树边还是虚边,所在子树的siz至少翻一倍,而这个性质在原来的线段树上是没有的

所以这个大bst的高度是\(O(\log{n})\)

当然,这个bst既不能旋转也不能splay,所以维护区间信息会比较吃力,但是,我们为什么要维护区间信息呢?这是动态dp啊,我们只需要维护这整个重链的矩阵连乘积就行了……,所以维护整个重链的连乘积还是可以做到的

另外这个东西看起来听玄学其实比树剖还好写……


这里对于对于全局平衡二叉树的时间复杂度进行证明。

在全局平衡二叉树上,每跳一个虚边,那么在原树上对应的就是子数大小乘2(最坏情况),同时每条一条实边,也就是重边(全局平衡二叉树上),由于我们每次的根都是带权重心,左右儿子相对平衡,所以也是子数大小乘2(最坏情况),那么就是\(log\)层了。

关于构造方法其实shadowice1984讲的特别好,Orz,奆佬还是奆佬,我还是蒟蒻QAQ。

代码与细节

上代码!

#include<cstdio>
#include<cstring>
#define  inf  999999999
#define  N  1100000
#define  M  2100000
using  namespace  std;
template  <class  T>
inline  void  getz(register  T  &x)
{
    x=0;
    register  T  f=1;register  char  c=getchar();
    while(c<'0'  ||  c>'9')c=='-'?f=-1:0,c=getchar();
    while(c<='9'  &&  c>='0')x=(x<<3)+(x<<1)+(c^48),c=getchar();
    x*=f;
}
template  <class  T>
inline  T  mymin(register  T  x,register  T  y){return  x<y?x:y;}//最小值 
template  <class  T>
inline  T  mymax(register  T  x,register  T  y){return  x>y?x:y;}//最大值 
int  zhi[N],n,m;//点值 
//---------------------------------------------------------
struct  bian
{
    int  y,next;
}a[M];int  last[N],len;
inline  void  ins(int  x,int  y){len++;a[len].y=y;a[len].next=last[x];last[x]=len;}
//边目录 
int  h[N],size[N],lsiz[N];
inline  void  dfs(int  x,int  fa)
{
    size[x]=1;
    for(int  k=last[x];k;k=a[k].next)
    {
        int  y=a[k].y;
        if(y!=fa)
        {
            dfs(y,x);
            size[x]+=size[y];if(size[y]>size[h[x]])h[x]=y;//改变重儿子 
        }
    }
    lsiz[x]=size[x]-size[h[x]];//表示的是在BST上他的权值 
}
//建立重心以及size 
struct  node
{
    int  mp[2][2];
    inline  node(){mp[0][0]=mp[0][1]=mp[1][0]=mp[1][1]=-inf;}
    inline  int*  operator [](int  x){return  mp[x];}
};
inline  node  operator*(node  x,node  y)//重定义乘法 
{
    node  ans;
    ans[0][0]=mymax(x[0][0]+y[0][0],x[0][1]+y[1][0]);
    ans[0][1]=mymax(x[0][0]+y[0][1],x[0][1]+y[1][1]);
    ans[1][0]=mymax(x[1][0]+y[0][0],x[1][1]+y[1][0]);
    ans[1][1]=mymax(x[1][0]+y[0][1],x[1][1]+y[1][1]);
    return  ans;
}
inline  void  mer(node  &x){x[0][0]=x[1][1]=0;x[0][1]=x[1][0]=-inf;}//等于单位矩阵 
inline  int  gtw(node  x){return  mymax(mymax(x[0][0],x[0][1]),mymax(x[1][0],x[1][1]));}
//矩阵 
struct  Tree
{
    node  mul[N]/*表示的是每个点在BST上管理的权值*/,w[N]/*每个点本身的权值*/;
    int  son[N][2],fa[N],root;
    //一下是基础函数 
    void  pre_do()//初始化
    {
        mer(mul[0]);mer(w[0]);//初始化
        for(int  i=1;i<=n;i++)w[i][0][0]=w[i][1][0]=0,w[i][0][1]=zhi[i];
    }
    void  ud(register  int  x){mul[x]=mul[son[x][0]]*w[x]*mul[son[x][1]];}//表示的是维护自己管理的值
    void  giv(register  int  x,register  int  v){w[x][1][0]=w[x][0][0]+=gtw(mul[v]);w[x][0][1]+=mymax(mul[v][0][0],mul[v][1][0]);fa[v]=x;}//表示v是x的轻儿子,让v认x父亲 
    bool  pd(register  int  f,register  int  x){return  !(son[f][0]!=x  &&  son[f][1]!=x);}//判断x是不是f的重儿子 
    //以下是建树 
    int  sta[N],top;//表示的是重链区间 
    bool  v[N];//是否被访问 
    int  bt(int  l,int  r)//返回根 
    {
        if(l>r)return  0;
        int  sum=0;
        for(register  int  i=l;i<=r;i++)sum+=lsiz[sta[i]];
        int  now=lsiz[sta[l]];
        for(register  int  i=l;i<=r;i++,now+=lsiz[sta[i]])
        {
            if(now*2>=sum)
            {
                int  rs=bt(l,i-1),ls=bt(i+1,r);fa[ls]=fa[rs]=sta[i];
                son[sta[i]][0]=ls;son[sta[i]][1]=rs;ud(sta[i]);
                return  sta[i];
            }
        }
    }
    int  sbuild(int  x)
    {
        for(register  int  i=x;i;i=h[i])v[i]=1;
        for(register  int  i=x;i;i=h[i])
        {
            for(register  int  k=last[i];k;k=a[k].next)
            {
                int  y=a[k].y;
                if(!v[y])giv(i,sbuild(y));
            }
        }
        top=0;for(register  int  i=x;i;i=h[i])sta[++top]=i;
        return  bt(1,top);
    }
    void  modify(int  x,int  W)//表示修改权值 
    {
        w[x][0][1]+=W-zhi[x];zhi[x]=W;//表示修改权值
        for(register  int  i=x;i;i=fa[i])
        {
            if(!pd(fa[i],i)  &&  fa[i])
            {
                w[fa[i]][0][0]-=gtw(mul[i]);w[fa[i]][1][0]=w[fa[i]][0][0];w[fa[i]][0][1]-=mymax(mul[i][0][0],mul[i][1][0]);
                ud(i);
                w[fa[i]][0][0]+=gtw(mul[i]);w[fa[i]][1][0]=w[fa[i]][0][0];w[fa[i]][0][1]+=mymax(mul[i][0][0],mul[i][1][0]);
            }
            else  ud(i);
        }
    }
}bst;
//全局平衡二叉树 
int  main()
{
    getz(n);getz(m);
    for(register  int  i=1;i<=n;i++)getz(zhi[i]);
    for(register  int  i=1;i<n;i++)
    {
        int  x,y;getz(x);getz(y);
        ins(x,y);ins(y,x);
    }
    dfs(1,0);
    bst.pre_do();
    bst.root=bst.sbuild(1);//初始化 
    register  int  lastans=0;
    for(register  int  i=1;i<=m;i++)
    {
        int  x,W;getz(x);getz(W);x^=lastans;
        bst.modify(x,W);
        printf("%d\n",lastans=gtw(bst.mul[bst.root]));
    }
    return  0;
}

问题1

在处理一条重链的时候,整个链的连乘我们知道,但是怎么知道单单对于这个重链以及他们轻儿子的答案?

这个问题很简单,就是直接提取出来,可以认为乘了一个矩阵:\([0,0]\),也就是这一行。

inline  int  gtw(node  x){return  mymax(mymax(x[0][0],x[0][1]),mymax(x[1][0],x[1][1]));}

问题2

在处理\(lf\)的时候,如何从儿子节点的矩阵中找到一点有用的信息?

例如知道一个节点的其中一个轻儿子在BST中的矩阵,但是如何从矩阵中翻出一点有用的东西???

也可以认为就是乘了一个\([0,0]\)矩阵然后的得到了轻儿子的\(f_{0,1}\)

猜你喜欢

转载自www.cnblogs.com/zhangjianjunab/p/12187184.html