动态 DP 总结

目录

  • 例题1:模拟赛题

    • 代码:
  • 例题2
  • 例题3:带修改树上最大独立集。

注:部分参考 https://www.luogu.org/blog/gkxx-is-here/what-the-hell-is-ddp

动态DP,就是一个十分简单的DP加了一个修改操作。
先看些例题:

例题1:模拟赛题

【问题描述】
某高校教学楼有 n 层,每一层有 2 个门,每层的两个门和下一层之间的两个门之
间各有一条路(共 4 条),相同层的 2 个门之间没有路。现给出如下两个操作:
0 x y : 查询第 x 层到第 y 层的路径数量。
1 x y z : 改变第 x 层 的 y 门 到第 x+1 层的 z 门的通断情况。
【输入】
输入文件名为(road.in)。
第一行:两个正整数 n m,表示共 n 层,m 个操作(2≤n≤50000,1≤m≤50000)接下
来 m 行,当第一个数为 0 的时候 后面有两个数 a,b (1≤a<b≤n)表示询问第 a 层
到第 b 层的路径数量。第一个数为 1 的时候,后面有三个数 x, y, z (1≤x<n,1≤y,z≤2)
表示改变第 x 层 的 y 门 到第 x+1 层的 z 门的通断情况。
【输出】
输出文件名为(road.out)。
输出每一个询问值。
答案对 10^9+7 取模

这是最简单的动态DP。
首先,发现有修改和询问,而询问又是区间查询,自然想到线段树维护。
直接的DP,肯定难以维护。考虑将\(dp_i到dp_{i+1}\)的变换转化为一个简单的操作。
这是个计数问题,只有求和,显然可以变为矩阵乘法。
就是说,通过矩乘优化,这个dp转化为了一段矩阵的乘积。
这样,问题变为:有一些矩阵,支持修改一个矩阵,和查询区间矩阵乘积。
线段树很容易维护。

代码:

#include <stdio.h>
#define ll long long
ll md=1000000007;
struct SJz
{
    ll jz[2][2];
    SJz operator*(SJz sz);
    void operator=(SJz sz)
    {
        jz[0][0]=sz.jz[0][0];
        jz[0][1]=sz.jz[0][1];
        jz[1][0]=sz.jz[1][0];
        jz[1][1]=sz.jz[1][1];
    }
};
SJz rtt,dw;
SJz SJz::operator*(SJz sz)
{
    for(int i=0;i<2;i++)
    {
        for(int j=0;j<2;j++)
        {
            rtt.jz[i][j]=0;
            for(int k=0;k<2;k++)
                rtt.jz[i][j]=(rtt.jz[i][j]+jz[i][k]*sz.jz[k][j])%md;
        }
    }
    return rtt;
}
SJz zh[200010];
void pushup(int i)
{
    zh[i]=zh[i<<1]*zh[(i<<1)|1];
}
void jianshu(int i,int l,int r)
{
    if(l+1==r)
    {
        zh[i].jz[0][0]=zh[i].jz[0][1]=zh[i].jz[1][0]=zh[i].jz[1][1]=1;
        return;
    }
    int m=(l+r)>>1;
    jianshu(i<<1,l,m);
    jianshu((i<<1)|1,m,r);
    pushup(i);
}
void xiugai(int i,int l,int r,int j,int x,int y)
{
    if(l+1==r)
    {
        zh[i].jz[x][y]^=1;
        return;
    }
    int m=(l+r)>>1;
    if(j<m)
        xiugai(i<<1,l,m,j,x,y);
    else
        xiugai((i<<1)|1,m,r,j,x,y);
    pushup(i);
}
SJz chaxun(int i,int l,int r,int L,int R)
{
    if(L<=l&&r<=R)
        return zh[i];
    if(r<=L||R<=l)
        return dw;
    SJz t1,t2;
    int m=(l+r)>>1;
    t1=chaxun(i<<1,l,m,L,R);
    t2=chaxun((i<<1)|1,m,r,L,R);
    return t1*t2;
}
int main()
{
    freopen("road.in","r",stdin);
    freopen("road.out","w",stdout);
    int n,m;
    scanf("%d%d",&n,&m);
    jianshu(1,1,n+1);
    dw.jz[0][0]=dw.jz[1][1]=1;
    dw.jz[0][1]=dw.jz[1][0]=0;
    for(int i=0;i<m;i++)
    {
        int lx;
        scanf("%d",&lx);
        if(lx==1)
        {
            int a,x,y;
            scanf("%d%d%d",&a,&x,&y);
            xiugai(1,1,n+1,a,x-1,y-1);
        }
        else
        {
            int x,y;
            scanf("%d%d",&x,&y);
            SJz jg=chaxun(1,1,n+1,x,y);
            printf("%I64d\n",(jg.jz[0][0]+jg.jz[0][1]+jg.jz[1][0]+jg.jz[1][1])%md);
        }
    }
    return 0;
}

非常好理解的。
然而,这是计数dp,只有加和乘,容易矩乘,但是通常的dp还是有\(min,max\)操作的。

例题2

和上题一样,考虑将转移表示为矩乘,然后线段树维护。
但是,矩乘没有\(min,max\)操作。
我们重新定义新的矩乘,** 使其满足结合律,以方便线段树维护 **。

这样,用类似上一题的方法维护即可。
没有代码。

例题3:带修改树上最大独立集。

这个树形DP转移很简单:

但是,这题是树,有多个儿子,不方便矩乘。

原文地址:https://www.cnblogs.com/lnzwz/p/11406209.html

时间: 2024-07-30 07:37:58

动态 DP 总结的相关文章

4712: 洪水 基于链分治的动态DP

国际惯例的题面:看起来很神的样子......如果我说这是动态DP的板子题你敢信?基于链分治的动态DP?说人话,就是树链剖分线段树维护DP.既然是DP,那就先得有转移方程.我们令f[i]表示让i子树中的叶子节点全部与根不联通,所需要的最小代价,v[i]为输入的点权.显然f[i]=min(v[i],sigma(f[soni])),边界条件是,如果i是叶子节点,则f[i]=v[i].我们需要用链分治去维护这个DP,所以要把DP拆成重链和轻链独立的形式.我们还是用f[i]表示让i子树中的叶子节点全部与根

[动态dp]线段树维护转移矩阵

背景:czy上课讲了新知识,从未见到过,总结一下. 所谓动态dp,是在动态规划的基础上,需要维护一些修改操作的算法. 这类题目分为如下三个步骤:(都是对于常系数齐次递推问题) 1先不考虑修改,不考虑区间,直接列出整个区间的dp方程.这个是基础,动态dp无论如何还是dp(这一步是一般是重点) 2.列出转移矩阵.由于有很多修改操作,我们将数据集中在一起处理,还可以利用矩阵结合律,并且区间比较好提取,(找一段矩阵就好了),修改也方便. 3.线段树维护矩阵.对于修改,我们就是在矩阵上进行修改,对于不同的

UOJ268 [清华集训2016] 数据交互 【动态DP】【堆】【树链剖分】【线段树】

题目分析: 不难发现可以用动态DP做. 题目相当于是要我求一条路径,所有与路径有交的链的代价加入进去,要求代价最大. 我们把链的代价分成两个部分:一部分将代价加入$LCA$之中,用$g$数组保存:另一部分将代价加在整条链上,用$d$数组保存. 这时候我们可以发现,一条从$u$到$v$的路径的代价相当于是$d[LCA(u,v)]+\sum_{x \in edge(u,v)}g[x]$. 如果是静态的,可以用树形DP解决. 看过<神奇的子图>的同学都知道,叶子结点是从它的儿子中取两个最大的出来,所

bzoj 4712 洪水——动态DP

题目:https://www.lydsy.com/JudgeOnline/problem.php?id=4712 因为作为动态DP练习而找到,所以就用动态DP做了,也没管那种二分的方法. 感觉理解似乎加深了. 果然初始权值也都是非负的. 所以 dp[cr] 表示当前子树与自己的叶子都断开了的最小代价,则 dp[cr]=min{ sigma dp[v] , w[cr] }(v是cr的直接孩子). 但这样的话,修改的时候需要把自己到根的路径都走一遍.不过查询是O(1)的,所以考虑分配一下. 走到根的

uoj#268. 【清华集训2016】数据交互(动态dp+堆)

传送门 动态dp我好像还真没咋做过--通过一个上午的努力光荣的获得了所有AC的人里面的倒数rk3 首先有一个我一点也不觉得显然的定理,如果两条路径相交,那么一定有一条路径的\(LCA\)在另一条路径上 于是我们可以对于每一个点记录两个值,一个\(a_i\)表示\(LCA\)在\(i\)点的所有路径的权值之和,一个是\(b_i\),表示经过点\(i\)且\(LCA\)不在点\(i\)的所有路径的权值之和 那么对于一条路径\((u,v)\),它的权值就是\(b_{LCA(u,v)}+\sum_{i\

回文串 --- 动态dp UVA 11584

题目链接: https://cn.vjudge.net/problem/34398/origin 本题的大意其实很简单,就是找回文串,大致的思路如下: 1. 确定一个回文串,这里用到了自定义的check函数原理如下: 传入le, ri两个值(定义从1开始), s+1 = aaadbccb. a a a d b c c b 1 2 3 4 5 6 7 8 比如,le = 5, ri = 8. 则s[5] == s[8]成立 le++ ri-- 再比较 s[6] == s[7]? 成立 le++,

SPOJ GSS3 (动态dp)

题意 题目链接 Sol 这题可以动态dp做. 设\(f[i]\)表示以\(i\)为结尾的最大子段和,\(g[i]\)表示\(1-i\)的最大子段和 那么 \(f[i] = max(f[i - 1] + a[i], a[i])\) \(g[i] = max(g[i - 1], f[i])\) 发现只跟前一项有关,而且\(g[i]从\)f[i]$转移过来的那一项可以直接拆开 那么构造矩阵 \[ \begin{bmatrix} a_{i} & -\infty & \dots a_{i} \\ a

LG4719 【模板】动态dp

题意 题目描述 给定一棵\(n\)个点的树,点带点权. 有\(m\)次操作,每次操作给定\(x,y\),表示修改点\(x\)的权值为\(y\). 你需要在每次操作之后求出这棵树的最大权独立集的权值大小. 输入输出格式 输入格式: 第一行,\(n,m\),分别代表点数和操作数. 第二行,\(V_1,V_2,...,V_n\),代表\(n\)个点的权值. 接下来\(n-1\)行,\(x,y\),描述这棵树的\(n-1\)条边. 接下来\(m\)行,\(x,y\),修改点\(x\)的权值为\(y\).

「校内训练 2019-04-23」越野赛车问题 动态dp+树的直径

题目传送门 http://192.168.21.187/problem/1236 http://47.100.137.146/problem/1236 题解 题目中要求的显然是那个状态下的直径嘛. 所以这道题有一个非常简单的做法--线段树分治. 直接把每一条边按照 \(l, r\) 的区间放到线段树上进行分治,遍历的时候用并查集维护直径就可以了. 时间复杂度为 \(O(n\log^2n)\). 很早以前就写了这个算法,代码附在了最后,不多讲了. 但是这道题还有一个方法--动态 DP. 线段树分治