动态dp和dp套dp
Posted 66t6
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了动态dp和dp套dp相关的知识,希望对你有一定的参考价值。
概述
这是两类特别的(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}=sumlimits_{yin son_x}{f_{y,0},f_{y,1}})
(f_{x,1}=sumlimits_{yin son_x}f_{y,0}+a_x)
这是考虑了所有儿子的情况,再加入不考虑重儿子(或者实儿子)的答案
(g_{x,0}=sumlimits_{yin lson_x}{f_{y,0},f_{y,1}})
(g_{x,1}=sumlimits_{yin 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}=maxlimits_k{A_{i,k}+B_{k,j}}),易证明这个运算是具有结合律但不具有交换律的
转移可以写成(egin{bmatrix}g_{x,0}&g_{x,0}\g_{x,1}&-inftyend{bmatrix}*egin{bmatrix}f_{y,0}\f_{y,1}end{bmatrix}=egin{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
",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
",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
",max(m.num[0][0],m.num[1][0]));
}
}
int main(void){
Read();
Solve();
return 0;
}
小可爱出题人一般是不会卡这(O(nlog^2n))的垃圾算法的
全局平衡二叉树
考虑我们好像真没用到重链剖分特殊的性质,看起来长链剖分,实链剖分都可以
但一个奇怪的事情就是(LCT)好像就可以做到(O(nlog n))只是常数过大
问题就在于(LCT)可以改树的形态,而我们并不需要,我们只需要把树做链剖分,保持树高(O(log n)),可以支持链上单点修改
食用论文《QTREE 解法的一些研究》可得"全局平衡二叉树"做法
方法很简单,对于每一条重链:
首先把重链铺平成一条长为(N)的序列
设(s_i=size_i-size_{hson_i})
求出最小的(i)使得(sumlimits_{j=1}^i s_igeqslant frac{1}{2}sumlimits_{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)的方法
引例
以上是关于动态dp和dp套dp的主要内容,如果未能解决你的问题,请参考以下文章
动态规划_计数类dp_数位统计dp_状态压缩dp_树形dp_记忆化搜索
[程序员代码面试指南]递归和动态规划-最长公共子串问题(DP,LCST)