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})

原文地址:https://www.cnblogs.com/zhangjianjunab/p/12187184.html