ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

动态 DP 总结

2019-08-24 21:57:26  阅读:256  来源: 互联网

标签:总结 SJz 动态 jz sz dp 例题 DP


目录

注:部分参考 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转移很简单:

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

标签:总结,SJz,动态,jz,sz,dp,例题,DP
来源: https://www.cnblogs.com/lnzwz/p/11406209.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有