概述
这是两类特别的\(dp\)种类,分别是带修改的\(dp\),与\(dp\)过程本身息息相关的\(dp\)
动态dp
概述
一些简单的\(dp\)如果带修改怎么办
如果状态是普通设法,修改一个值的话包含这个值的所有情况都会被改,均摊\(O(n)\)
一种粗暴的看法,我们可以让所有状态包含的值均摊,比如用倍增划分区间的设法,让均摊被包含的状态数变为\(\log\),但这个说法很模糊,我们并不知道什么状态可以倍增
事实上,这牵扯到一个很重要的东西,"转移"这个运算有结合律否
如果有的话,我们就可以一段一段的维护,合并,在树上就可以通过各种平衡树或树剖分成一条一条重链来处理
很多矩阵的新定义都具有结合律
多余的提一句,在树上,刚才这种方式又可以用只\(dp\)关键点的的虚树\(dp\)替代
问题
\(\mathtt{BSOJ5290}\)支持修改点权,动态求树的最大独立集
考虑普通\(dp\)
设\(f_{i,0/1}\)表示\(i\)点不选/选子树的最大独立集,答案就是\(\max\{f_{1,0},f_{1,1}\}\)
\(f_{x,0}=\sum\limits_{y\in son_x}\{f_{y,0},f_{y,1}\}\)
\(f_{x,1}=\sum\limits_{y\in son_x}f_{y,0}+a_x\)
这是考虑了所有儿子的情况,再加入不考虑重儿子(或者实儿子)的答案
\(g_{x,0}=\sum\limits_{y\in lson_x}\{f_{y,0},f_{y,1}\}\)
\(g_{x,1}=\sum\limits_{y\in lson_x}f_{y,0}+a_x\)
那么\(f_{x,0}=g_{x,0}+\max\{f_{hson_x,0},f_{hson_x,1}\}\)
\(f_{x,1}=g_{x,1}+f_{hson_x,0}\)
定义矩阵新运算\(C=A*B\)表示\(C_{i,j}=\max\limits_k\{A_{i,k}+B_{k,j}\}\),易证明这个运算是具有结合律但不具有交换律的
转移可以写成\(\begin{bmatrix}g_{x,0}&g_{x,0}\\g_{x,1}&-\infty\end{bmatrix}*\begin{bmatrix}f_{y,0}\\f_{y,1}\end{bmatrix}=\begin{bmatrix}f_{x,0}\\f_{x,1}\end{bmatrix}(y=hson_x)\)
对每条重链(事实上是待测点和所在重链底部中间部分)维护矩阵连乘积即可,其实你可以注意到这个成绩的顺序是自底向上的,因此对\(dfn\)为下标的线段树上是先右后左
更新就是跳重链到根,\(O(1)\)改底部的\(g\)值(因为这种边是轻边才会影响\(g\)),\(O(\log?)\)改重链区间连乘积
很久以前写的代码
#include<cstdio>
#include<algorithm>
#include<string.h>
#define re register
#define ls(x) ((x)<<1)
#define rs(x) (((x)<<1)|1)
#define N 200005
using namespace std;
struct Matrix{
int num[2][2];
inline Matrix(void){memset(num,0,sizeof num);}
inline friend Matrix operator*(re Matrix a,re Matrix b){
re int i,j,k;re Matrix c;
for(i=0;i<2;++i)for(j=0;j<2;++j)for(k=0;k<2;++k)c.num[i][j]=max(c.num[i][j],a.num[i][k]+b.num[k][j]);
return c;
}
}ldpm[N<<1],t[N<<1];
int n,q,dp[N][2],size[N],son[N],pos[N],st[N],ed[N],antipos[N],h[N],cnt,scnt,ldp[N][2],top[N],val[N],dep[N],fa[N];
struct Edge{
int to,next;
}e[N<<1];
inline void AddEdge(re int x,re int y){e[++cnt]=(Edge){y,h[x]};h[x]=cnt;}
inline void pushup(re int x){t[x]=t[ls(x)]*t[rs(x)];}
inline void dfs1(re int x,re int depth,re int prt){
// printf("%d %d %d\n",x,depth,prt);
re int i,y;dep[x]=depth;size[x]=1;
dp[x][1]=val[x];fa[x]=prt;
for(i=h[x];i;i=e[i].next){
y=e[i].to;if(prt==y)continue;
dfs1(y,depth+1,x);
size[x]+=size[y];
if(!son[x]||size[y]>size[son[x]])son[x]=y;
dp[x][1]+=max(0,*dp[y]);
*dp[x]+=max(0,max(*dp[y],dp[y][1]));
// printf("%d->%d %d %d\n",x,y,dp[x][1],*dp[x]);
}
}
inline void dfs2(re int x,re int Top,re int prt){
re int i,y;
top[x]=Top;pos[x]=++scnt;antipos[scnt]=x;ldp[x][1]=val[x];st[x]=scnt;ed[Top]=scnt;
if(son[x])dfs2(son[x],Top,x);
for(i=h[x];i;i=e[i].next){
y=e[i].to;if(y==prt||y==son[x])continue;
dfs2(y,y,x);
ldp[x][1]+=max(0,*dp[y]);*ldp[x]+=max(0,max(*dp[y],dp[y][1]));
}
}
inline void Change(re int pos,re int l,re int r,re int k){
if(l==r){t[pos]=ldpm[l];return;}
re int mid=(l+r)>>1;
if(k<=mid)Change(ls(pos),l,mid,k);
else Change(rs(pos),mid+1,r,k);
pushup(pos);
}
inline Matrix Query(re int pos,re int l,re int r,re int ql,re int qr){
if(ql<=l&&r<=qr) return t[pos];
int mid=(l+r)/2;
if(qr<=mid)return Query(ls(pos),l,mid,ql,qr);
if(ql>mid)return Query(rs(pos),mid+1,r,ql,qr);
return Query(ls(pos),l,mid,ql,qr)*Query(rs(pos),mid+1,r,ql,qr);
}
inline Matrix Ask(re int x){return Query(1,1,scnt,st[top[x]],ed[top[x]]);}
inline void Build(re int pos,re int l,re int r){
if(l>r)return ;
if(l==r){t[pos]=ldpm[l];return;}//!
re int mid=(l+r)>>1;
Build(ls(pos),l,mid);Build(rs(pos),mid+1,r);
pushup(pos);
}
inline void Change(re int x,re int y){
re Matrix past,now;
ldpm[st[x]].num[1][0]+=(y-val[x]);val[x]=y;
while(x){
past=Ask(top[x]);
Change(1,1,scnt,st[x]);
now=Ask(top[x]);
x=fa[top[x]];
ldpm[st[x]].num[0][0]+=max(now.num[0][0],now.num[1][0])-max(past.num[0][0],past.num[1][0]);
ldpm[st[x]].num[0][1]=ldpm[st[x]].num[0][0];
ldpm[st[x]].num[1][0]+=now.num[0][0]-past.num[0][0];
}
}
inline void Read(void){
re int i,j,x,y;
scanf("%d%d",&n,&q);
for(i=1;i<=n;++i)scanf("%d",val+i);
for(i=1;i<n;++i){
scanf("%d%d",&x,&y);
AddEdge(x,y);AddEdge(y,x);
}
dfs1(1,1,0);dfs2(1,1,0);
for(i=1;i<=n;++i){
ldpm[st[i]].num[0][0]=ldpm[st[i]].num[0][1]=ldp[i][0];
ldpm[st[i]].num[1][0]=ldp[i][1];
// cout<<'q'<<ldp[i][0]<<' '<<ldp[i][1]<<' '<<top[i]<<' '<<son[i]<<endl;
}
Build(1,1,scnt);
}
inline void Solve(void){
re int x,y;re Matrix m;
while(q--){
scanf("%d%d",&x,&y);
Change(x,y);
m=Ask(1);
printf("%d\n",max(m.num[0][0],m.num[1][0]));
}
}
int main(void){
Read();
Solve();
return 0;
}
小可爱出题人一般是不会卡这\(O(n\log^2n)\)的垃圾算法的
全局平衡二叉树
考虑我们好像真没用到重链剖分特殊的性质,看起来长链剖分,实链剖分都可以
但一个奇怪的事情就是\(LCT\)好像就可以做到\(O(n\log n)\)只是常数过大
问题就在于\(LCT\)可以改树的形态,而我们并不需要,我们只需要把树做链剖分,保持树高\(O(\log n)\),可以支持链上单点修改
食用论文《QTREE 解法的一些研究》可得"全局平衡二叉树"做法
方法很简单,对于每一条重链:
首先把重链铺平成一条长为\(N\)的序列
设\(s_i=size_i-size_{hson_i}\)
求出最小的\(i\)使得\(\sum\limits_{j=1}^i s_i\geqslant \frac{1}{2}\sum\limits_{j=1}^N s_i\),并用这个点作为这条重链的根,实边连本重链,虚边接上面重链
\(e.g.\)
(来自\(\mathtt{\color{red}Fee\_cle6418}\)的课件)
对于建树部分是这样的
inline int Build(re int l,re int r){
if(l>r)return 0;
re int pos=lower_bound(s+l,s+r+1,(s[r]-s[l-1]-1)/2+1+s[l-1])-s,ls,rs,x;
x=st[pos],ls=Build(l,pos-1),rs=Build(pos+1,r);
fa[*son[x]=ls]=fa[son[x][1]=rs]=x;pushup(x);
return x;
}
inline int dfs(re int x){
re int i,j,y,rt,top;
for(i=x;i;i=bson[i])vis[i]=1;
for(i=x;i;i=bson[i])
for(j=h[i];j;j=e[j].next){
if(vis[y=e[j].to])continue;
fa[rt=dfs(y)]=i;
a[i].a[1][1]+=max(b[rt].a[1][1],b[rt].a[2][1]);
a[i].a[1][2]=a[i].a[1][1];
a[i].a[2][1]+=b[rt].a[1][1];
}
for(top=0,i=x;i;i=bson[i])st[++top]=i,s[top]=s[top-1]+size[i]-size[bson[i]];
return Build(1,top);
}
全部的话在下面
注意
虽然说矩阵转移方便推广但事实上矩阵乘法由于自带\(C^3\)的常数,很多题卡不过去,有一些原始的\(dp\)如区间最大子段和的可删堆解法就可以沿用到树上,因为堆具有可合并性
例题
dp套dp
概述
顾名思义,解决奇怪的涉及\(dp\)过程的\(dp\)问题,常见形式有计数对象满足一定\(dp\)结果
\(e.g.\)求\(LCS(A,B)=K\)且\(B\)满足\(P\)条件方案数 或 不包含给定子串的\(N\)位\(K\)进制数个数
很容易发现这两类问题如果转化为判定问题就真的是普式\(dp\)
一般的解决思路就是在解决原始判定\(dp\)过程中计数
更专业的解释直接说明本质:将内层\(DP\)的结果作为外层\(DP\)的状态进行\(DP\)的方法
引例
原文地址:https://www.cnblogs.com/66t6/p/12381068.html