该来的还是来了,我是真的不想写这个狗博客,是真的烦。
参考文献
很详细: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})。