DDP,即动态动态规划,可以用于解决一类带修改的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])\)

这个DP每次修改显然要\(O(n)\)

我们考虑到好多在DP的时候,我们都用矩阵来加速递推。

我们现在引入全新的思想,如何将它改写成矩阵呢?

其实矩阵乘法能够成立,依赖的是乘法对加法有分配律。之后我们发现,加法对取\(min/max\)的操作也是有分配律的。比如\(a+max(b,c) = max(a+b,a+c)\)

那么我们完全可以考虑重新定义矩阵乘法,使得其满足如下的运算:\(C[i][j] = max\{A[i][k]+B[k][j]\}\)

这样的话……刚才的转移方程,我们就可以改写成如下的形式了。

\[\begin{bmatrix} a_i & -\infty & a_i \\ a_i & 0 &a_i \\ -\infty & -\infty & 0\end{bmatrix}\times \begin{bmatrix} f_{i-1}\\ g_{i-1} \\ 0\end{bmatrix}\quad = \begin{bmatrix}f_i \\ g_i \\ 0\end{bmatrix}
\]

那么我们就可以用线段树维护区间矩阵乘积来计算答案了。

#include<bits/stdc++.h>
#define rep(i,a,n) for(int i = a;i <= n;i++)
#define per(i,n,a) for(int i = n;i >= a;i--)
#define enter putchar('\n')
#define fr friend inline
#define y1 poj
#define pr pair<int,int>
#define fi first
#define sc second
#define mp make_pair using namespace std;
typedef long long ll;
const int M = 200005;
const int INF = 1e9+7;
const double eps = 1e-7; int read()
{
int ans = 0,op = 1;char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') op = -1;ch = getchar();}
while(ch >= '0' && ch <= '9') ans = ans * 10 + ch - '0',ch = getchar();
return ans * op;
} struct matrix
{
int f[3][3];
matrix(){memset(f,0,sizeof(f));}
void change(int x)
{
f[0][0] = f[1][0] = f[0][2] = f[1][2] = x;
f[0][1] = f[2][0] = f[2][1] = -INF;
}
friend matrix operator + (const matrix &a,const matrix &b)
{
matrix c;
rep(i,0,2) rep(j,0,2) c.f[i][j] = -INF;
rep(k,0,2)
rep(i,0,2)
rep(j,0,2)
c.f[i][j] = max(c.f[i][j],a.f[i][k] + b.f[k][j]);
return c;
}
}; struct node
{
matrix mat;
}t[M<<2]; int n,q,x,y,op; void build(int p,int l,int r)
{
if(l == r) {t[p].mat.change(read());return;}
int mid = (l+r) >> 1;
build(p<<1,l,mid),build(p<<1|1,mid+1,r);
t[p].mat = t[p<<1].mat + t[p<<1|1].mat;
} void modify(int p,int l,int r,int pos,int val)
{
if(l == r) {t[p].mat.change(val);return;}
int mid = (l+r) >> 1;
if(pos <= mid) modify(p<<1,l,mid,pos,val);
else modify(p<<1|1,mid+1,r,pos,val);
t[p].mat = t[p<<1].mat + t[p<<1|1].mat;
} matrix query(int p,int l,int r,int kl,int kr)
{
if(l == kl && r == kr) return t[p].mat;
int mid = (l+r) >> 1;
if(kr <= mid) return query(p<<1,l,mid,kl,kr);
else if(kl > mid) return query(p<<1|1,mid+1,r,kl,kr);
else return query(p<<1,l,mid,kl,mid) + query(p<<1|1,mid+1,r,mid+1,kr);
} int main()
{
n = read(),build(1,1,n),q = read();
while(q--)
{
op = read(),x = read(),y = read();
if(op == 0) modify(1,1,n,x,y);
else
{
matrix k = query(1,1,n,x,y);
printf("%d\n",max(k.f[1][0],k.f[1][2]));
}
}
return 0;
}

之后我们再来考虑下一个问题。树上最大独立集。

用\(f[i][0]\)表示不选i,子树中最大独立集的大小,\(f[i][1]\)表示选i,子树中最大独立集的大小。

显然有\(f[i][0] = \sum max(f[v][0],f[v][1]),f[i][1] = \sum f[v][0] + a[i]\)

我们要把这玩意改写成矩阵的形式。但是我们首先要使用数据结构维护树,比如树剖。(LCT版的我还不会)

因为树剖可以把重链整成一段连续的区间,那么我们先把与重链无关的一些东西提取出来。这样,我们设\(g[i][0/1]\)表示不取/取i,i的非重儿子中最大独立集的大小

这样的话,dp的方程就变成了这样:\(f[i][0] =g[i][0] + max(f[son[i]][0],f[son[i]][1]),f[i][1] = g[i][1] + f[son[i]][0]\)

然后就可以开心的写成矩阵的形式:

\[\begin{bmatrix} g[i][0] & g[i][0] \\ g[i][1] & -\infty\end{bmatrix}\times \begin{bmatrix} f[son[i][0]]\\ f[son[i]][1] \end{bmatrix}= \begin{bmatrix}f[i][0] \\ f[i][1]\end{bmatrix}
\]

那么现在我们就可以用树剖+矩阵去维护了。这个和普通的树剖有一些区别,就是我们需要先跑一次树DP来计算出来f,g数组,之后初始化矩阵,每次从修改点跳重链跳到根节点,注意每次跳重链的时候要取一段完整的重链,所以我们还需要额外记录链的底部在哪。

然后就不大难修改了。线段树和上面基本是一样的,树剖也比较简单,修改过程就是一个先减再加的过程。

看一下luogu的模板

#include<bits/stdc++.h>
#define rep(i,a,n) for(int i = a;i <= n;i++)
#define per(i,n,a) for(int i = n;i >= a;i--)
#define enter putchar('\n')
#define pr pair<int,int>
#define mp make_pair
#define fi first
#define sc second
using namespace std;
typedef long long ll;
const int M = 200005;
const int N = 10000005;
const int INF = 1e9; int read()
{
int ans = 0,op = 1;char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') op = -1;ch = getchar();}
while(ch >='0' && ch <= '9') ans = ans * 10 + ch - '0',ch = getchar();
return ans * op;
} struct matrix
{
int f[2][2];
matrix(){memset(f,0,sizeof(f));}
friend matrix operator + (const matrix &a,const matrix &b)
{
matrix c;
rep(i,0,1)
rep(j,0,1) c.f[i][j] = -INF;
rep(k,0,1)
rep(i,0,1)
rep(j,0,1) c.f[i][j] = max(c.f[i][j],a.f[i][k] + b.f[k][j]);
return c;
}
}val[M]; struct node
{
matrix mat;
}t[M<<1]; struct edge
{
int next,to,from;
}e[M<<1]; int n,m,head[M],ecnt,v[M],top[M],fa[M],hson[M],size[M];
int ed[M],x,y,pos[M],dfn[M],idx,F[M][2];
void add(int x,int y) {e[++ecnt] = {head[x],y,x},head[x] = ecnt;} void dfs1(int x,int f)
{
size[x] = 1,fa[x] = f;
for(int i = head[x];i;i = e[i].next)
{
if(e[i].to == f) continue;
dfs1(e[i].to,x),size[x] += size[e[i].to];
if(size[e[i].to] > size[hson[x]]) hson[x] = e[i].to;
}
} void dfs2(int x,int t)
{
dfn[x] = ++idx,pos[idx] = x,top[x] = t,ed[t] = max(ed[t],idx);
F[x][0] = 0,F[x][1] = v[x];
val[x].f[0][0] = val[x].f[0][1] = 0,val[x].f[1][0] = v[x];
if(hson[x])
{
int v = hson[x];
dfs2(v,t),F[x][0] += max(F[v][0],F[v][1]),F[x][1] += F[v][0];
}
for(int i = head[x];i;i = e[i].next)
{
int v = e[i].to;
if(v == fa[x] || v == hson[x]) continue;
dfs2(v,v),F[x][0] += max(F[v][0],F[v][1]),F[x][1] += F[v][0];
val[x].f[0][0] += max(F[v][0],F[v][1]);
val[x].f[0][1] = val[x].f[0][0],val[x].f[1][0] += F[v][0];
}
} void build(int p,int l,int r)
{
if(l == r) {t[p].mat = val[pos[l]];return;}
int mid = (l+r) >> 1;
build(p<<1,l,mid),build(p<<1|1,mid+1,r);
t[p].mat = t[p<<1].mat + t[p<<1|1].mat;
} void modify(int p,int l,int r,int x)
{
if(l == r){t[p].mat = val[pos[x]];return;}
int mid = (l+r) >> 1;
if(x <= mid) modify(p<<1,l,mid,x);
else modify(p<<1|1,mid+1,r,x);
t[p].mat = t[p<<1].mat + t[p<<1|1].mat;
} matrix query(int p,int l,int r,int kl,int kr)
{
if(l == kl && r == kr) return t[p].mat;
int mid = (l+r) >> 1;
if(kr <= mid) return query(p<<1,l,mid,kl,kr);
else if(kl > mid) return query(p<<1|1,mid+1,r,kl,kr);
else return query(p<<1,l,mid,kl,mid) + query(p<<1|1,mid+1,r,mid+1,kr);
} void uprange(int x,int y)
{
val[x].f[1][0] += y - v[x],v[x] = y;
matrix A,B;
while(x)
{
B = query(1,1,n,dfn[top[x]],ed[top[x]]),modify(1,1,n,dfn[x]);
A = query(1,1,n,dfn[top[x]],ed[top[x]]),x = fa[top[x]];
val[x].f[0][0] += max(A.f[0][0],A.f[1][0]) - max(B.f[0][0],B.f[1][0]);
val[x].f[0][1] = val[x].f[0][0];
val[x].f[1][0] += (A.f[0][0] - B.f[0][0]);
}
} int main()
{
n = read(),m = read();
rep(i,1,n) v[i] = read();
rep(i,1,n-1) x = read(),y = read(),add(x,y),add(y,x);
dfs1(1,0),dfs2(1,1),build(1,1,n);
while(m--)
{
x = read(),y = read(),uprange(x,y);
matrix ans = query(1,1,n,dfn[1],ed[1]);
printf("%d\n",max(ans.f[0][0],ans.f[1][0]));
}
return 0;
}

DDP入门的更多相关文章

  1. 布客&#183;ApacheCN 翻译/校对/笔记整理活动进度公告 2020.1

    注意 请贡献者查看参与方式,然后直接在 ISSUE 中认领. 翻译/校对三个文档就可以申请当负责人,我们会把你拉进合伙人群.翻译/校对五个文档的贡献者,可以申请实习证明. 请私聊片刻(52981514 ...

  2. Meteor入门

    转载Meteor入门介绍   Meteor是什么 基于nodejs的实时web APP开发框架. Meteor能带来什么 简单的说,你可以用js搞定客户端.服务端的开发.另外,客户端.服务端的界限被极 ...

  3. Web API 入门 一

    因为只是是一个简单的入门.所有暂时不去研究web API一些规范.比如RESTful API 这里有个接收RESTful API的.RESTful API 什么是WebApi 看这里:http://w ...

  4. Meteor入门介绍

    Meteor是什么 基于nodejs的实时web APP开发框架. Meteor能带来什么 简单的说,你可以用js搞定客户端.服务端的开发.另外,客户端.服务端的界限被极大的模糊.客户端的界面跟服务端 ...

  5. [No00004F]史上最全Vim快捷键键位图(入门到进阶)vim常用命令总结

    在命令状态下对当前行用== (连按=两次), 或对多行用n==(n是自然数)表示自动缩进从当前行起的下面n行.你可以试试把代码缩进任意打乱再用n==排版,相当于一般IDE里的code format.使 ...

  6. [推荐] 网络侦查工具 NMAP 简单入门

    [推荐] 网络侦查工具 NMAP 简单入门 # 前言 作为一只运维开发,总是避不开要和网络打交道的.尤其是当自身能力到达瓶颈,开始从事云计算以求突破.会有搭建多台虚拟机的需要,这时候如果在手工的查询 ...

  7. [源码解析] PyTorch 分布式(17) --- 结合DDP和分布式 RPC 框架

    [源码解析] PyTorch 分布式(17) --- 结合DDP和分布式 RPC 框架 目录 [源码解析] PyTorch 分布式(17) --- 结合DDP和分布式 RPC 框架 0x00 摘要 0 ...

  8. Angular2入门系列教程7-HTTP(一)-使用Angular2自带的http进行网络请求

    上一篇:Angular2入门系列教程6-路由(二)-使用多层级路由并在在路由中传递复杂参数 感觉这篇不是很好写,因为涉及到网络请求,如果采用真实的网络请求,这个例子大家拿到手估计还要自己写一个web ...

  9. ABP入门系列(1)——学习Abp框架之实操演练

    作为.Net工地搬砖长工一名,一直致力于挖坑(Bug)填坑(Debug),但技术却不见长进.也曾热情于新技术的学习,憧憬过成为技术大拿.从前端到后端,从bootstrap到javascript,从py ...

随机推荐

  1. Android NDK开发常见错误

    错误一: make: *** No rule to make target `/cygdrive/d/1-workspace/showmap-android-opengles/jni/showmap_ ...

  2. typedef struct与struct定义结构体

    今天在定义结构体的时候发现typedef struct与struct定义结构体有一些不同之处: 结构也是一种数据类型, 能够使用结构变量, 因此,  象其他 类型的变量一样, 在使用结构变量时要先对其 ...

  3. Ubuntu下安装Oracle JRE运行环境

    安装Oracle JDK -linux-i586.tar.gz安装参见在Ubuntu下利用Eclipse调试FFmpeg Linux x64:链接:http://pan.baidu.com/s/1gd ...

  4. Oracle学习第二篇—单行函数

    1字符函数 length  字符长度 lengthb 字节长度 lower 变为小写 upper 变为大写 initcap 首字母大写 select Lower('xun Ying') 小写,Uppe ...

  5. C# 串口发送 陷阱,必须知道的坑

    目的:间隔100毫秒持续发送指令 由于 C#串口发送为同步方式发送,发送占用时间较长,导致发送变慢, 自己写工具并手工测试两种波特率发送占用时长如下

  6. ES5中的类与继承

    最近在重新复习TypeScript,看到类这块的时候自然会和ES5中的类写法进行对比加深印象. 发现ES5的类与继承一些细节还是挺多的,时间久了容易忘记,特此记录下. 首先是ES5的类定义,这没什么好 ...

  7. mysql 二:操作表

    的存储.在操作表之前,首先要用选定数据库,因为表都是建立在对应的数据库里面的.在这里我们使用之前建立的test数据库 mysql> use test; Database changed 创建表的 ...

  8. SAP FI 科目代码

    资产类 现金 银行存款 其他货币资金 短期投资 短期投资跌价准备 应收票据 应收股利 应收利息 应收账款 其他应收款 坏账准备 预付账款 应收补贴款 物料采购 原材料 包装物 低值易耗品 材料成本差异 ...

  9. 《高性能Javascript》 Summary(三)

    第八章.编程实践 Programming Practices 经验: 避免使用 eval_r()和Function构造器避免二次评估.此外,给setTimeout()和setInterval()函数传 ...

  10. margin的相关属性及应用

    1.margin常见问题: ①IE6下双边距   (不推荐使用float+margin,可用padding替代) 详见<css浏览器兼容问题集锦>之4.IE6中margin双边距 ②IE6 ...