动态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\)的方法

引例

原文地址:https://www.cnblogs.com/66t6/p/12381068.html

时间: 2024-10-07 17:18:40

动态dp和dp套dp的相关文章

【POJ 2750】 Potted Flower(线段树套dp)

[POJ 2750] Potted Flower(线段树套dp) Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 4566   Accepted: 1739 Description The little cat takes over the management of a new park. There is a large circular statue in the center of the park, surrou

【BZOJ3864】Hero meet devil DP套DP

[BZOJ3864]Hero meet devil Description There is an old country and the king fell in love with a devil. The devil always asks the king to do some crazy things. Although the king used to be wise and beloved by his people. Now he is just like a boy in lo

[CTSC2017]最长上升自序列(伪题解)(树状数组+DP套DP+最小费用最大流+Johnson最短路+Yang_Tableau)

部分分做法很多,但每想出来一个也就多5-10分.正解还不会,下面是各种部分分做法: Subtask 1:k=1 LCS长度最长为1,也就是说不存在j>i和a[j]>a[i]同时成立.显然就是一个LDS,树状数组直接求即可. Subtask 2:k=2 最多两个,也就是可以由两个LCS拼起来,f[i][j]表示第一个LCS以i结尾,第二个以j结尾的方案数,转移显然. Subtask 3:k=2 树状数组优化DP,复杂度由$O(n^3)$降为$O(n^2 \log n)$ Subtask 4,5:

HDU4960Another OCD Patient(区间dp,分块后再DP)

Another OCD Patient Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Others) Total Submission(s): 716    Accepted Submission(s): 270 Problem Description Xiaoji is an OCD (obsessive-compulsive disorder) patient. This morni

嵌套矩形 DAG上的dp(深搜+dp)

题目链接:http://acm.nyist.net/JudgeOnline/problem.php?pid=16 矩形嵌套 时间限制:3000 ms  |  内存限制:65535 KB 难度:4 描述 有n个矩形,每个矩形可以用a,b来描述,表示长和宽.矩形X(a,b)可以嵌套在矩形Y(c,d)中当且仅当a<c,b<d或者b<c,a<d(相当于旋转X90度).例如(1,5)可以嵌套在(6,2)内,但不能嵌套在(3,4)中.你的任务是选出尽可能多的矩形排成一行,使得除最后一个外,每一

dp乱写2:状态压缩dp(状压dp)炮兵阵地

https://www.luogu.org/problem/show?pid=2704 题意: 炮兵在地图上的摆放位子只能在平地('P') 炮兵可以攻击上下左右各两格的格子: 而高原('H')上炮兵能够攻击到但是不能摆放 求最多能摆放的炮兵的数量 就是这个意思. 难度提高,弱省省选 一开始是想写dfs(迷之八皇后)的, 但是看到数据量100就想dp了: 因为题目n的范围给的很少n<=10,想到状压 非常明显是一个状态压缩的dp(状压dp) 其实可以当做状压的入门题目来做. 由于本行的状态是由前若

hoj 2662 经典状压dp // MyFirst 状压dp

题目链接:http://acm.hit.edu.cn/hoj/problem/view?id=2662 1.引言:用dp解决一个问题的时候很重要的一环就是状态的表示,一般来说,一个数组即可保存状态. 但是有这样的一类题目,它们具有dp问题的特性,但状态中所包含的信息过多,如果要用数组来保存状态的话需要四维以上的数组. 于是,就要通过状态压缩来保存状态,而使用状态压缩来保存状态的dp就叫做状态压缩dp. 2.状态压缩dp的特点:状态中的某一维会比较小,一般不会超过15,多了的话状态数会急剧上升而无

dp乱写3:论dp在不在dp中(但在dp范畴)内的应用

最近正儿八经的学习了dp,有一些题目非常明显看出来就是dp了比如说:过河卒.方格取数.导弹拦截.加分二叉树.炮兵阵地更加明显的还有:采药.装箱问题.过河.金明的预算方案.今天来谈谈dp的dp在不在dp中(但在dp范畴)内的应用(简称dp的应用)dp其实可以用贪心来优化,有些基本不可能的情况就可以直接省略了.dp其实可以用数据结构来优化,取最大值最小值用堆...dp其实不一定是dp,也可以是一种思想,的简称dp思想,就是用前面的一个或者两个状态来推出现在状态的可能,解决一些问题有神效.dp的空间基

DP专题&#183;四(树形dp)

1.poj 115 TELE 题意:一个树型网络上有n个结点,1~n-m为信号传送器,n-m+1~n为观众,当信号传送给观众后,观众会付费观看,每铺设一条道路需要一定费用.现在求以1为根,使得收到观众的费用-铺设道路的费用>=0的情况下,能最多给多少个观众观看? 思路:树形dp,dp[i][j]表示以i为根的子树中选择j个观众(叶子)最大的收益. ①如果当前结点为叶子结点,那么其dp[i][0]=0,dp[i][1]=val[i]. ②如果为其他结点,则dp[i][j]=max(dp[i][j]