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. POJ 2253 Frogger(最小最大距离)

    题意  给你n个点的坐标  求第1个点到第2个点的全部路径中两点间最大距离的最小值 非常水的floyd咯 #include<cstdio> #include<cmath> #i ...

  2. 贷前系统ElasticSearch实践总结

    贷前系统负责从进件到放款前所有业务流程的实现,其中涉及一些数据量较大.条件多样且复杂的综合查询,引入ElasticSearch主要是为了提高查询效率,并希望基于ElasticSearch快速实现一个简 ...

  3. iOS开发系列--让你的应用“动”起来【转载】

    概览 原文链接:http://www.cnblogs.com/kenshincui/p/3972100.html 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥i ...

  4. FTP匿名登录或弱口令漏洞及服务加固

    漏洞描述 FTP 弱口令或匿名登录漏洞,一般指使用 FTP 的用户启用了匿名登录功能,或系统口令的长度太短.复杂度不够.仅包含数字.或仅包含字母等,容易被黑客攻击,发生恶意文件上传或更严重的入侵行为. ...

  5. FileOutPutStream 的写操作

    package xinhuiji_day07; import java.io.File;import java.io.FileNotFoundException;import java.io.File ...

  6. 渐变背景(background)效果

    chrom and Safari浏览器: webkit核心的浏览器.使用CSS3渐变方法(css-gradient) -webkit-gradient(type, start_point, end_p ...

  7. iOS中对于多个按钮,选中其中一个,其他按钮选中状态为NO

      第一,定义一个全局的按钮变量 /** *  按钮选中,中间值 */ @property (nonatomic,strong) UIButton *selectedBtn; 第二,添加多个按钮到sc ...

  8. TP框架---thinkphp基础知识

    php框架    发瑞 一.真实项目开发步骤: 多人同时开发项目,协作开发项目.分工合理.效率有提高(代码风格不一样.分工不好) 测试阶段 上线运行 对项目进行维护.修改.升级(单个人维护项目,十分困 ...

  9. 模块:(日期选择)jquery、bootstrap实现日期下拉选择+bootstrap jquery UI自带动画的日期选择器

    一:jquery.bootstrap实现日期下拉选择 点击文本框弹出窗口 弹窗显示日期时间选择下拉 年份取当前年份的前后各5年 天数随年份和月份的变化而变化 点击保存,文本框中显示选中的日期 代码部分 ...

  10. 【BZOJ2666】[cqoi2012]组装 贪心

    [BZOJ2666][cqoi2012]组装 Description 数轴上有m个生产车间可以生产零件.一共有n种零件,编号为1~n.第i个车间的坐标为xi,生产第pi种零件(1<=pi< ...