动态 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_{i+1}\)等于\(dp_i\)乘一个矩阵。
这样,通过矩乘优化,这个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转移很简单:

但是,这题是树,有多个儿子,不方便矩乘。
通常,若序列上用线段树,那么树上就是树剖套线段树。
但是,转移时针对所有儿子而言的,而树剖只有一个重儿子。
所以,我们只能额外维护一个信息g,表示一个节点只算上它和它的的轻儿子的dp值,然后再用这个g和它的重儿子的dp值算出它的dp值。
将这个\(dp_v\)到\(dp_u\)的过程写成矩阵乘法,矩阵中包含g。
这样,某个点的dp值就是重链上矩阵的乘积,用线段树维护。
考虑修改:dp是通过g计算的,所以维护g即可。而修改一个点后,只有轻边上的g值会被修改,沿着重链跳到根即可。
时间复杂度\(O(2^3*qlog^2n)\),能过\(10^5\)。
代码:
(常数巨大)
#include <stdio.h>
#define max(a,b) ((a)>(b)?(a):(b))
int inf=2100000000;
struct SJz
{
int jz[2][2];
SJz(){}
SJz(int a,int b,int c,int d)
{
jz[0][0]=a;jz[0][1]=b;
jz[1][0]=c;jz[1][1]=d;
}
void operator=(SJz x)
{
jz[0][0]=x.jz[0][0];
jz[0][1]=x.jz[0][1];
jz[1][0]=x.jz[1][0];
jz[1][1]=x.jz[1][1];
}
};
SJz operator*(SJz x,SJz y)
{
SJz rt;
for(int i=0;i<2;i++)
{
for(int j=0;j<2;j++)
rt.jz[i][j]=-inf;
}
for(int i=0;i<2;i++)
{
for(int j=0;j<2;j++)
{
for(int k=0;k<2;k++)
{
if(x.jz[i][j]!=-inf&&y.jz[j][k]!=-inf)
rt.jz[i][k]=max(x.jz[i][j]+y.jz[j][k],rt.jz[i][k]);
}
}
}
return rt;
}
int fr[100010],ne[200010],v[200010],bs=0;
void addb(int a,int b)
{
v[bs]=b;
ne[bs]=fr[a];
fr[a]=bs++;
}
int fa[100010],son[100010],top[100010],dn[100010];
int xl[100010],sz[100010],wz[100010],tm=0;
int g0[100010],g1[100010],f0[100010],f1[100010];
int dfs1(int u,int f)
{
fa[u]=f;son[u]=-1;
int ma=0,he=1;
for(int i=fr[u];i!=-1;i=ne[i])
{
if(v[i]==f)
continue;
int rt=dfs1(v[i],u);
he+=rt;
if(rt>ma)
{
ma=rt;
son[u]=v[i];
}
}
return he;
}
void dfs2(int u,int f,int tp)
{
top[u]=tp;
wz[u]=++tm;xl[wz[u]]=u;
if(son[u]==-1)
{
dn[u]=u;
return;
}
dfs2(son[u],u,tp);
for(int i=fr[u];i!=-1;i=ne[i])
{
if(v[i]!=f&&v[i]!=son[u])
dfs2(v[i],u,v[i]);
}
dn[u]=dn[son[u]];
}
void dfs3(int u,int f)
{
f0[u]=0;f1[u]=sz[u];
g0[u]=0;g1[u]=sz[u];
for(int i=fr[u];i!=-1;i=ne[i])
{
if(v[i]==f)
continue;
dfs3(v[i],u);
int r0=f0[v[i]],r1=f1[v[i]];
if(v[i]!=son[u])
{
g0[u]+=max(r0,r1);
g1[u]+=r0;
}
f0[u]+=max(r0,r1);
f1[u]+=r0;
}
}
SJz ji[400010],I(0,-inf,-inf,0);
void ddz(int i,int l,int r)
{
int u=xl[l];
ji[i]=SJz(g0[u],g1[u],g0[u],-inf);
}
void jianshu(int i,int l,int r)
{
if(l+1==r)
{
ddz(i,l,r);
return;
}
int m=(l+r)>>1;
jianshu(i<<1,l,m);
jianshu((i<<1)|1,m,r);
ji[i]=ji[(i<<1)|1]*ji[i<<1];
}
void xiugai(int i,int l,int r,int j)
{
if(l+1==r)
{
ddz(i,l,r);
return;
}
int m=(l+r)>>1;
if(j<m)
xiugai(i<<1,l,m,j);
else
xiugai((i<<1)|1,m,r,j);
ji[i]=ji[(i<<1)|1]*ji[i<<1];
}
SJz getsum(int i,int l,int r,int L,int R)
{
if(R<=l||r<=L)
return I;
if(L<=l&&r<=R)
return ji[i];
int m=(l+r)>>1;
return getsum((i<<1)|1,m,r,L,R)*getsum(i<<1,l,m,L,R);
}
void getf(int u,int &f0,int &f1)
{
SJz rt=getsum(1,1,tm+1,wz[u],wz[dn[u]]);
int r0=0,r1=sz[dn[u]];
f0=max(r0+rt.jz[0][0],r1+rt.jz[1][0]);
f1=max(r0+rt.jz[0][1],r1+rt.jz[1][1]);
}
void update(int u,int n0,int n1)
{
if(fa[u]!=0)
{
g0[fa[u]]=g0[fa[u]]-max(f0[u],f1[u])+max(n0,n1);
g1[fa[u]]=g1[fa[u]]-f0[u]+n0;
xiugai(1,1,tm+1,wz[fa[u]]);
}
f0[u]=n0;f1[u]=n1;
}
void xiugai(int x,int y)
{
g1[x]=g1[x]-sz[x]+y;
sz[x]=y;
xiugai(1,1,tm+1,wz[x]);
while(x!=0)
{
x=top[x];
int n0,n1;
getf(x,n0,n1);
update(x,n0,n1);
x=fa[x];
}
}
void build()
{
dfs1(1,0);
dfs2(1,0,1);
dfs3(1,0);
jianshu(1,1,tm+1);
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&sz[i]);
fr[i]=-1;
}
for(int i=0;i<n-1;i++)
{
int a,b;
scanf("%d%d",&a,&b);
addb(a,b);addb(b,a);
}
build();
for(int i=0;i<m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
xiugai(x,y);
printf("%d\n",max(f0[1],f1[1]));
}
return 0;
}
动态 DP 总结的更多相关文章
- 动态DP之全局平衡二叉树
目录 前置知识 全局平衡二叉树 大致介绍 建图过程 修改过程 询问过程 时间复杂度的证明 板题 前置知识 在学习如何使用全局平衡二叉树之前,你首先要知道如何使用树链剖分解决动态DP问题.这里仅做一个简 ...
- Luogu P4643 【模板】动态dp
题目链接 Luogu P4643 题解 猫锟在WC2018讲的黑科技--动态DP,就是一个画风正常的DP问题再加上一个动态修改操作,就像这道题一样.(这道题也是PPT中的例题) 动态DP的一个套路是把 ...
- 动态dp学习笔记
我们经常会遇到一些问题,是一些dp的模型,但是加上了什么待修改强制在线之类的,十分毒瘤,如果能有一个模式化的东西解决这类问题就会非常好. 给定一棵n个点的树,点带点权. 有m次操作,每次操作给定x,y ...
- 洛谷P4719 动态dp
动态DP其实挺简单一个东西. 把DP值的定义改成去掉重儿子之后的DP值. 重链上的答案就用线段树/lct维护,维护子段/矩阵都可以.其实本质上差不多... 修改的时候在log个线段树上修改.轻儿子所在 ...
- 动态 DP 学习笔记
不得不承认,去年提高组 D2T3 对动态 DP 起到了良好的普及效果. 动态 DP 主要用于解决一类问题.这类问题一般原本都是较为简单的树上 DP 问题,但是被套上了丧心病狂的修改点权的操作.举个例子 ...
- 动态dp初探
动态dp初探 动态区间最大子段和问题 给出长度为\(n\)的序列和\(m\)次操作,每次修改一个元素的值或查询区间的最大字段和(SP1714 GSS3). 设\(f[i]\)为以下标\(i\)结尾的最 ...
- [总结] 动态DP学习笔记
学习了一下动态DP 问题的来源: 给定一棵 \(n\) 个节点的树,点有点权,有 \(m\) 次修改单点点权的操作,回答每次操作之后的最大带权独立集大小. 首先一个显然的 \(O(nm)\) 的做法就 ...
- UOJ268 [清华集训2016] 数据交互 【动态DP】【堆】【树链剖分】【线段树】
题目分析: 不难发现可以用动态DP做. 题目相当于是要我求一条路径,所有与路径有交的链的代价加入进去,要求代价最大. 我们把链的代价分成两个部分:一部分将代价加入$LCA$之中,用$g$数组保存:另一 ...
- [复习]动态dp
[复习]动态dp 你还是可以认为我原来写的动态dp就是在扯蛋. [Luogu4719][模板]动态dp 首先作为一个\(dp\)题,我们显然可以每次修改之后都进行暴力\(dp\),设\(f[i][0/ ...
- 【BZOJ4911】[SDOI2017]切树游戏(动态dp,FWT)
[BZOJ4911][SDOI2017]切树游戏(动态dp,FWT) 题面 BZOJ 洛谷 LOJ 题解 首先考虑如何暴力\(dp\),设\(f[i][S]\)表示当前以\(i\)节点为根节点,联通子 ...
随机推荐
- QT 读写.ini配置文件
当需要存放的数据量较少时合适使用.ini配置文件. #include <QCoreApplication> #include <QSettings> void SystemSe ...
- 题解 CF1216C 【White Sheet】
虽然也很水,但这道还是比前两道难多了... 题目大意:给你三个位于同一平面直角坐标系的矩形,询问你后两个是否完全覆盖了前一个 首先,最直观的想法应该是,把第一个矩形内部每个整数点检查一下,看看是否位于 ...
- SPA中使用jwt
什么是jwt? JSON Web Token (JWT),它是目前最流行的跨域身份验证解决方案 JWT的工作原理 1. 是在服务器身份验证之后,将生成一个JSON对象并将其发送回用户,示例如下:{&q ...
- 一行代码实现Vue微信支付,无需引用wexin-sdk库,前后端分离HTML微信支付,无需引用任何库
前后端分离项目实现微信支付的流程: 1:用户点击支付 2:请求服务端获取支付参数 3:客户端通过JS调起微信支付(微信打开的网页) * 本文主要解决的是第3步,视为前两步已经完成,能正确拿到支付参数, ...
- Java之协程(quasar)
一.前面我们简单的说了一下,Python中的协程原理.这里补充Java的协程实现过程.有需要可以查看python之协程. 二.Java协程,其实做Java这么久我也没有怎么听过Java协程的东西,但是 ...
- Django 报错总结
报错: AttributeError: 'NoneType' object has no attribute 'split' 最近在写网站中遇到一个问题,就是题目上所写的:AttributeError ...
- 2019最新Web前端经典面试试题(含答案)
1,阐述清楚浮动的几种方式(常见问题)(1)父级div定义 height原理:父级div手动定义height,就解决了父级div无法自动获取到高度的问题. 优点:简单.代码少.容易掌握 缺点:只适合高 ...
- DBShop前台RCE
前言 处理重装系统的Controller在判断是否有锁文件后用的是重定向而不是exit,这样后面的逻辑代码还是会执行,导致了数据库重装漏洞和RCE. 正文 InstallController.php中 ...
- Oracle数据库账户口令复杂度-等保测评之身份鉴别
1. 默认情况下数据库没有启用密码验证函数功能,可通过下面sql查询 SQL> select limit from dba_profiles where RESOURCE_NAME='P ...
- pygame安装遇到的坑
坑一:python版本冲突,电脑同时安装多个版本的python,由于每个都是python.exe,cmd命令窗口输入的python不一定是你想要的版本,所以最好还是安装单个版本即可. 坑二:由于电脑安 ...