• 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}=sumlimits_{v∈u.son}max(f_{v,1},dp_{v,0}))
    (f_{u,1}=sumlimits_{v∈u.son}f_{v,0}+a_{u})

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

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

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

    在这里插入图片描述
    然后我们对于每个点,我们记录一个(lf)值:
    (lf_{u,0}=sumlimits_{v∈u.lightson}max(f_{v,0},f_{v,1}))
    (lf_{u,1}=sumlimits_{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))

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

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

    用树链剖分维护的话,现在就是(O(nlog^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
    ",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})

  • 相关阅读:
    梯度提升树(GBDT)原理小结
    scikit-learn Adaboost类库使用小结
    集成学习之Adaboost算法原理小结
    集成学习原理小结
    支持向量机高斯核调参小结
    scikit-learn 支持向量机算法库使用小结
    支持向量机原理(五)线性支持回归
    支持向量机原理(四)SMO算法原理
    支持向量机原理(三)线性不可分支持向量机与核函数
    支持向量机原理(二) 线性支持向量机的软间隔最大化模型
  • 原文地址:https://www.cnblogs.com/zhangjianjunab/p/12187184.html
Copyright © 2020-2023  润新知