动态dp(ddp)小记

待完善

动态dp

前置技能:矩阵乘法、树剖/LCT、dp

模板题 P4719

\(n\) 个点的树,点带点权,有 \(m\) 次操作给定 \(x,y\) 表示修改 \(x\) 的权值为 \(y\),每次操作后求出这棵树的最大独立集的权值大小

最大权独立集

在图/树上选若干结点使得无直接连边,称为独立集

最大权独立集就是点权和最大的独立集

不带修改

不带修怎么做

\(dp_{i,0}\) 表示 \(i\) 不在独立集时以 \(i\) 为根的子树内的最大权和

同理 \(dp_{i,1}\) 表示 \(i\) 在独立集时的

显然有树形dp

\[dp_{u,0}=\sum_{v\in son_u} \max(dp_{v,0},dp_{v,1})\\ dp_{u,1}=val(u)+\sum_{v\in son_u} f_{v,0} \]

暴力做法

我们看方程可以发现,修改一个点的权值只会对他自己和他的祖先产生影响,于是每次修改操作实际上我们需要更改的是他到根的路径上所有点的dp值

优化

这个往上跳的过程能搞得算法一般就是俩,树剖或者倍增,这还有修改,那么相信树剖的力量吧

有一个小问题,就是树剖套的那个线段树啊,他只能维护具有结合律的信息,显然啊这个dp的递推他是不满足结合律的

能不能有一个东西,又满足结合律又可以辅助dp递推呢

诶就是矩阵

还有就是原来的方程不是很利于优化,我们要针对树剖搞一下

\[g_{u,0}=\sum_{v\in lightson_u} \max(f_{v,0},f_{v,1})\\ g_{u,1}=val(u)+\sum_{v\in lightson_u} f_{v,0} \]

好像没啥区别?

不对, \(g\) 里面存的不是所有儿子,只是轻儿子的dp值

那么 \(f\) 可以简化处理了(此处 \(son_u\) 表示重儿砸)

\[f_{u,0}=g_{u,0}+\max(f_{son_u,0},f_{son_u,1})\\ f_{u,1}=g_{u,1}+val(u)+f_{son_u,0} \]

我们稍微做点手脚,我们不用编号来dp,我们用dfs序来dp,那么这个 \(f\) 的式子又可以变形一下

\[f_{u,0}=g_{u,0}+\max(f_{u+1,0},f_{u+1,1})\\ f_{u,1}=g_{u,1}+val(u)+f_{u+1,0} \]

对于叶子,有 \(f_{i,0}=0,f_{i,1}=val(i)\)

于是我们可以构建一个很奇怪转移矩阵

\[\begin{bmatrix} g_{i,0}+\max(f_{i+1,0},f_{i+1,1})\\ (g_{i,1}+val(i))+f_{i+1,0} \end{bmatrix} =\begin{bmatrix} f_{i,0}\\ f_{i,1} \end{bmatrix} \]

这个矩阵好像不太对劲,我们正常的矩阵乘法貌似是用乘法和加法啊

矩阵重载

是这样的,我们考虑要用不是加法和乘法的两种运算去构建矩阵

考察一下加法和乘法具有的性质

  • 乘法交换律
  • 乘法分配律
  • 加法交换律

这三个性质共同支撑了矩阵乘法的结合律

我们以 \(\max\) 和加法再来重载一个矩阵乘法

注意到加法对 \(\max\) 具有分配律,于是我们用加法代替乘法,取 \(\max\) 代替加法可以构建一个新的矩阵乘法

如果说原来的矩阵乘法形如

\[C(i,j)=\sum_{k=1}^n A(i,k)\times B(k,j) \]

现在就是

\[C(i,j)=\max_{k=1}^{n} A(i,k)+B(k.j) \]

我们不妨用 \(\otimes\) 来代指一下这样重载的新乘法

式子

\[\begin{bmatrix} g_{i,0}+\max(f_{i+1,0},f_{i+1,1})\\ (g_{i,1}+val(i))+f_{i+1,0} \end{bmatrix}= \begin{bmatrix} \max(g_{i,0}+f_{i+1,0},g_{i,0}+f_{i+1,1})\\ \max((g_{i,1}+val(i))+f_{i+1,0},-\infty+f_{i+1,0}) \end{bmatrix}= \begin{bmatrix} f_{i,0}\\ f_{i,1} \end{bmatrix} \]

这样做的意义是什么?

我们注意到

\[\begin{bmatrix} \max(g_{i,0}+f_{i+1,0},g_{i,0}+f_{i+1,1})\\ \max((g_{i,1}+val(i))+f_{i+1,0},-\infty+f_{i+1,0}) \end{bmatrix}= \begin{bmatrix} g_{i,0},g_{i,0} \\ g_{i,1}+val(i),-\infty \end{bmatrix} \otimes \begin{bmatrix} f_{i+1,0}\\f_{i+1,1} \end{bmatrix} \]

也就是说

\[\begin{bmatrix} f_{i,0}\\ f_{i,1} \end{bmatrix} = \begin{bmatrix} g_{i,0},g_{i,0} \\ g_{i,1}+val(i),-\infty \end{bmatrix} \otimes \begin{bmatrix} f_{i+1,0}\\f_{i+1,1} \end{bmatrix} \]

务必注意现在的矩阵乘法并没有交换律,左乘右乘别写反了,是左乘

由于每个节点的 \(g\) 不同,所以每个节点都需要维护一个

\[\begin{bmatrix} g_{i,0},g_{i,0} \\ g_{i,1}+val(i),-\infty \end{bmatrix} \]

维护

我们不能在每个点存下 \(f\),这是不现实的

如何现场算

首先叶子结点我们已知,我们接下来还会维护一个重链上转移矩阵的线段树

好像好办了

假设查询 \(x\),我们记 \(end(x)\) 表示 \(x\) 所在重链的底端,则初始矩阵为

\[\begin{bmatrix} 0\\val(end(x)) \end{bmatrix} \]

同时为了方便我们给线段树结点中叶子结点里对应的结点都放一个新的矩阵乘法的单位元,也就是

\[\begin{bmatrix} 0,-\infty\\ -\infty,0 \end{bmatrix} \]

言尽于此,实现参考板子题吧,修改查询那一坨我实在不知道怎么描述

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
const int inf=0x3f3f3f3f;
//需要注意不能用INT_MAX,会溢出的


//前向星存图
struct edge{
    int v,nxt;
}e[N<<1];
int tot,hd[N];
void add(int u,int v){
    e[++tot]={v,hd[u]},hd[u]=tot;
}

int n,m;

//封装矩阵乘法
template<int row,int col>
struct Matix{
    int r,c;
    int ele[row][col];
    Matix():r(row),c(col){};
    int& operator()(int a,int b){
        return ele[a][b];
    }
};
template<int n,int m,int p>
auto operator*(Matix<n,m> m1,Matix<m,p> m2 ){
    Matix<n,p> ret;
    memset(ret.ele,-inf,sizeof ret.ele);
    for(int i=0;i<n;i++){
        for(int k=0;k<m;k++){
            for(int j=0;j<p;j++){
                ret(i,j)=max(ret(i,j), m1(i,k)+m2(k,j));
            }
        }
    }
    return ret;
}
Matix<2,2> ident,g[N];//单位矩阵和每个点对应的矩阵

//dp相关定义
int val[N],f[2][N];//f用于初始化g


//树剖第一遍dfs,无区别
int dep[N],fa[N],siz[N],son[N];
void dfs1(int u,int f){
    fa[u]=f,dep[u]=dep[f]+1,siz[u]=1;
    for(int i=hd[u];i;i=e[i].nxt){
        int v=e[i].v;
        if(v!=f){
            dfs1(v,u);
            siz[u]+=siz[v];
            if(siz[son[u]]<siz[v]) son[u]=v;    
        }
    }
}

//树剖第二遍,初始化矩阵池
int tim,top[N],dfn[N],nfd[N],edd[N];
void dfs2(int u,int tp){
    top[u]=tp;
    dfn[u]=++tim;
    nfd[tim]=u;
    if(!son[u]){
        f[1][u]=val[u];
        f[0][u]=0;
        g[u]=ident;
        edd[u]=u;
        return;
    }
    g[u](1,0)=val[u],g[u](1,1)=-inf;
    dfs2(son[u],tp);
    edd[u]=edd[son[u]];
    for(int i=hd[u];i;i=e[i].nxt){
        int v=e[i].v;
        if(v==fa[u] or v==son[u]) continue;
        dfs2(v,v);
        g[u](0,0)=g[u](0,1)+=max(f[0][v],f[1][v]);
        g[u](1,0)+=f[0][v];
    }
    f[0][u]=g[u](0,0)+max(f[0][son[u]],f[1][son[u]]);
    f[1][u]=g[u](1,0)+f[0][son[u]];
}

//线段树
Matix<2,2> V[N*4];
#define ls(x) (x<<1)
#define rs(x) (x<<1|1)
#define mid ((l+r)>>1)
#define pushup(x) V[x]=V[ls(x)]*V[rs(x)];
void build(int p,int l,int r){
    if(l==r){
        V[p]=g[nfd[l]];
        return;
    }
    build(ls(p),l,mid);
    build(rs(p),mid+1,r);
    pushup(p);
}

auto query(int p,int l,int r,int L,int R){
    if(l>R or r<L) return ident;
    if(L<=l and r<=R) return V[p];
    Matix<2,2> ret=ident;
    if(L<=mid) ret=ret*query(ls(p),l,mid,L,R);
    if(mid<R) ret=ret*query(rs(p),mid+1,r,L,R);
    return ret;
}
void modi(int p,int l,int r,int pos,int x){
    if(l==r){
        V[p]=g[x];
        return;
    }
    if(pos<=mid) modi(ls(p),l,mid,pos,x);
    else modi(rs(p),mid+1,r,pos,x);
    pushup(p);
}
auto queryt(int x){
    Matix<2,1> tmp;
    tmp(0,0)=0,tmp(1,0)=val[edd[x]];
    return query(1,1,n,dfn[x],dfn[edd[x]])*tmp;
}
void modit(int x,int z){
    Matix<2,1> od,nw;
    g[x](1,0)+=z-val[x];
    // cerr<<x<<" "<<top[x]<<"WTF\n";
    od=queryt(top[x]);
    val[x]=z;
    while(x){
        modi(1,1,n,dfn[x],x);
        nw=queryt(top[x]);
        x=fa[top[x]];
        g[x](0,0)=g[x](0,1)+=max(nw(0,0),nw(1,0))-max(od(0,0),od(1,0));
        g[x](1,0)+=nw(0,0)-od(0,0);
        od=queryt(top[x]);
    }
}

int main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n>>m;
    ident(1,0)=ident(0,1)=-inf;
    for(int i=1;i<=n;i++) cin>>val[i];
    for(int i=1;i<n;i++){
        int u,v;
        cin>>u>>v;
        add(u,v);
        add(v,u);
    }
    dfs1(1,0);
    dfs2(1,1);
    build(1,1,n);
    while(m--){
        int x,z;
        cin>>x>>z;
        modit(x,z);
        auto ans=queryt(1);
        cout<<max(ans(0,0),ans(1,0))<<"\n";
    }
}

有待进一步更新

posted @ 2024-10-29 19:11  exut  阅读(6)  评论(0编辑  收藏  举报
Title