动态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}=\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); 
}

全部的话在下面

\(\mathtt{Code}\)

注意

虽然说矩阵转移方便推广但事实上矩阵乘法由于自带\(C^3\)的常数,很多题卡不过去,有一些原始的\(dp\)如区间最大子段和的可删堆解法就可以沿用到树上,因为堆具有可合并性

例题

dp套dp

概述

顾名思义,解决奇怪的涉及\(dp\)过程的\(dp\)问题,常见形式有计数对象满足一定\(dp\)结果

\(e.g.\)\(LCS(A,B)=K\)\(B\)满足\(P\)条件方案数 或 不包含给定子串的\(N\)\(K\)进制数个数

很容易发现这两类问题如果转化为判定问题就真的是普式\(dp\)

一般的解决思路就是在解决原始判定\(dp\)过程中计数

更专业的解释直接说明本质:将内层\(DP\)的结果作为外层\(DP\)的状态进行\(DP\)的方法

引例

猜你喜欢

转载自www.cnblogs.com/66t6/p/12381068.html
DP
DP?
今日推荐