DDP入门

2023-11-19

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;
}

转载于:https://www.cnblogs.com/captain1/p/10459348.html

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

DDP入门 的相关文章

  • 数据结构中常见的树(BST二叉搜索树、AVL平衡二叉树、RBT红黑树、B-树、B+树、B*树)

    原文 http blog csdn net sup heaven article details 39313731 数据结构中常见的树 BST二叉搜索树 AVL平衡二叉树 RBT红黑树 B 树 B 树 B 树 转载 2014年09月16日
  • 《Linux From Scratch》第三部分:构建LFS系统 第六章:安装基本的系统软件- 6.29. Coreutils-8.23...

    Coreutils 软件包包含用于显示和设置基本系统特性的工具 大概编译时间 2 5 SBU 需要磁盘空间 193 MB 6 29 1 安装 Coreutils POSIX 要求 Coreutils 中的程序即使在多字节语言环境也能正确识别
  • python 历险记(五)— python 中的模块

    目录 前言 基础 模块化程序设计 模块化有哪些好处 什么是 python 中的模块 引入模块有几种方式 模块的查找顺序 模块中包含执行语句的情况 用 dir 函数来窥探模块 python 的内置模块有哪些 结语 参考文档 系列文章列表 前言
  • 树06--二叉树中和为某一值的路径

    树06 二叉树中和为某一值的路径 jz24 题目概述 解析 参考答案 注意事项 说明 题目概述 算法说明 输入一颗二叉树的根节点和一个整数 按字典序打印出二叉树中结点值的和为输入整数的所有路径 路径定义为从树的根结点开始往下一直到叶结点所经
  • 将二叉树转为有序的双向链表

    一 题目要求 输入一棵二叉排序树 现在要将该二叉排序树转换成一个有序的双向链表 而且在转换的过程中 不能创建任何新的结点 只能调整树中的结点指针的指向来实现 include
  • 链表和线性表的优缺点

    链表和线性表的优缺点 作为我们最先接触的两个数据结构 链表和线性表的优缺点都较为明显 并且二者互相补足 文章目录 链表和线性表的优缺点 线性表 线性表的组成 线性表的缺点 线性表的优点 链表 链表的组成 链表的优点 链表的缺点 总结 线性表
  • 逆波兰表达式求值(C语言实现)

    实验项目 从文本文件输入任意一个语法正确的 中缀 表达式 显示并保存该表达式 利用栈结构 把上述 中缀 表达式转换成后缀表达式 并显示栈的状态变化过程和所得到的后缀表达式 利用栈结构 对上述后缀表达式进行求值 并显示栈的状态变化过程和最终结
  • 手把手教你实现一个向量

    文章目录 什么是向量 向量提供哪些接口 实现 宏定义 定义类 成员变量 构造函数与析构函数 构造函数 析构函数 成员函数 size get r put r e expand insert r e remove lo hi remove r
  • 亚利桑那州立大学周纵苇:研习 U-Net ——现有的分割网络创新

    雷锋网AI研习社按 经典的 Encoder Decoder 结构在目标分割问题中展现出了举足轻重的作用 然而这样一个相对固定的框架使得模型在感受野大小和边界分割精度两方面很难达到兼顾 本次公开课 讲者以 U Net 为案例分析 总结现有的分
  • 数据结构——计算节点个数和二叉树高度(C语言版)

    摘自 数据结构 计算节点个数和二叉树高度 C语言版 作者 正弦定理 发布时间 2020 12 12 23 27 09 网址 https blog csdn net chinesekobe article details 111086664
  • 算法学习——贪心算法之币种统计

    算法描述 币种统计 单位给每一位员工发工资 精确到元 为了保证不临时换零钱 使得每个员工取款的张数最少 在取工资前统计所有员工所需要的各种票面的张数 约定票种为100 50 20 10 5 2 1元 并验证币种统计是否正确 算法思路 算法描
  • 数据结构与算法-列表(双向链表)设计及其排序算法

    0 概述 本文主要涵盖列表 双向链表 的设计及其排序算法的总结 列表是一种典型的动态存储结构 其中的数据 分散为一系列称作节点 node 的单位 节点之间通过指针相互索引和访问 为了引入新节点或删除原有节点 只需在局部调整少量相关节点之间的
  • 数理统计知识整理——回归分析与方差分析

    题记 时值我的北科研究生第一年下 选学 统计优化 课程 备考促学 成此笔记 以谨记 1 线性回归 1 1 原理分析 要研究最大积雪深度x与灌溉面积y之间的关系 测试得到近10年的数据如下表 使用线性回归的方法可以估计x与y之间的线性关系 线
  • 区块链中的哈希算法

    区块链中的密码学 密码学在区块链中的应用主要有两个 哈希算法与非对称加密算法 这次主要对哈希算法进行详细的说明 哈希算法 哈希算法的特点有 1 输入可以为任意大小的字符串 2 产生固定大小的输出 3 可以在合理的时间内算出输出值 若要满足密
  • 用两个栈实现队列

    目录 一 栈的基本结构及其接口 二 我的队列结构定义 三 我的队列创建及其初始化 四 我的队列入队 五 我的队列出队 六 我的队列取队头元素 七 我的队列判空 八 我的队列销毁 一 栈的基本结构及其接口 栈的结构定义 typedef int
  • 【数据结构入门精讲 | 第二篇】一文讲清算法复杂度

    上篇文章中我们引入了算法 数据结构 数据类型等概念 而要想衡量一个算法与数据结构是否为优质的 就需要一个衡量标准 这个衡量标准也是在我们实现一个好的算法时要遵循的原则 目录 基本概念 渐进性态 渐进性态数学表征 算法复杂度的运算 顺序搜索算
  • 【数据结构】双链表的定义和操作

    目录 1 双链表的定义 2 双链表的创建和初始化 3 双链表的插入节点操作 4 双链表的删除节点操作 5 双链表的查找节点操作 6 双链表的更新节点操作 7 完整代码 嗨 我是 Filotimo 很高兴与大家相识 希望我的博客能对你有所帮助
  • 按照层次遍历结果打印完全二叉树

    按照层次遍历结果打印完全二叉树 按照推论结果 l 层首个节点位置 2 h l 1 l 层节点间距 2 h l 1 1 编码实现 public static
  • 排序:计数排序

    一 概念 计数排序是非比较排序 是对哈希直接定址法的变形应用 二 思想 利用数组统计相同数据出现的次数 例如整型数据m出现n次 就在数组m位置记录数据为n 最后从头遍历数组打印数据即可 通俗来讲就是 数组下标即为数据 下标所指位置的值即为数
  • 高精度运算合集,加减乘除,快速幂,详细代码,OJ链接

    文章目录 零 前言 一 加法 高精度加法步骤 P1601 A B 二 减法 高精度减法步骤

随机推荐

  • 代码随想录算法训练营第一天

    Leetcode704 二分查找 题目链接 关键词 二分查找 循环不变量 区间 问题思路 二分查找的应用 关键在于循环过程中区间的维护 记住循环不变量原则 在这个问题中循环不变量是区间的定义 注意左闭右开和左开右闭的区别 class Sol
  • Apache POI组件操作Excel

    Apache的POI组件是Java操作Microsoft Office办公套件的强大API 其中对Word Excel和PowperPoint都有支持 当然使用较多的还是Excel 因为Word和PowerPoint用程序动态操作的应用较少
  • proto学习

    介绍 protocol buffers 是一种语言无关 平台无关 可扩展的序列化结构数据的方法 它可用于通信协议 数据存储等 protocol buffers的接口 c java python API doc link https deve
  • 用Python画一个生日蛋糕并写上生日祝福对象及生日祝福语

    用Python画一个生日蛋糕并写上生日祝福对象及生日祝福语 画一个双层蛋糕并点上蜡烛 代码运行时间较长 请静待惊喜出现 代码运行截图 完整程序代码 干货主要有 200 多本 Python 电子书 和经典的书籍 应该有 Python标准库资料
  • 掌握 Ajax,第 7 部分: 在请求和响应中使用 XML

    偶尔使用 Ajax 的开发人员也会注意到 Ajax 中的 x 并意识到它代表 XML XML 是编程中最常用的数据格式之一 对于异步应用程序中的服务器响应能够带来切实的好处 在本文中 您将看到服务器如何在请求响应中发送 XML 现在如果不使
  • Java设计模式:装饰者模式(Decorator Pattern)

    装饰者模式 涉及的重要设计原则 类应该对扩展开放 对修改关闭 装饰者模式定义 装饰者模式动态地将责任附加到对象上 若要扩展功能 装饰者提供了比继承更有弹性的替代方案 UML类图 装饰者模式事例 咖啡店 咖啡种类 1 深焙咖啡 DarkRoa
  • Python中如何使用boolean类型的数据

    在写代码的过程中 遇到了定义boolean类型变量的问题 之前一直试图用java或者c定义布尔变量的方法 一直不奏效 经过一旦学习之后才明白 和java竟然只是大小写的问题 在python中将java中的true携程True 将false携
  • Educational Codeforces Round 149 (Rated for Div. 2)A~D

    Grasshopper on a Line 题意 给出n和k 求从0到n最少走几步 以及步长 要求步长不能整除k 思路 从n往下找到 k不等于0的数 输出该数和n 该数即可 如果n k 0 那就只需要一步 代码 gt File Name a
  • 探索Java8——默认方法

    文章目录 什么是默认方法 不断演进的API 初始版本API 第二版API 概述默认方法 什么是默认方法 在传统的Java程序中 实现接口的方式是通过Implements把接口中的每一个方法提供一个实现 或者从父类继承他的实现 然而 在实际开
  • redis搜索 - KEYS命令

    文章目录 KEYS命令 使用 使用场景 KEYS命令 KEYS命令用于搜索匹配某个模式的所有key 例如常见的keys 命令 会返回所有的键 Time complexity O N 使用 KEYS命令支持以下正则匹配模式 h llo mat
  • [STM32]详解单片机GPIO输入模式配置-上拉下拉与浮空

    前面说到单片机的GPIO主要输出模式主要有推挽模式和开漏模式 除了连接到片内外设的模拟输入模式和复用输入功能以外 这里再说一下通用输入模式配置 STM32单片机的通用输入模式主要有输入浮空 输入上拉与输入下拉 当配置成上拉模式 即GPIO
  • python rsa加密之后byte类型存储到数据库中_python3 rsa加密

    遇到了跟你一样的问题 此js封装的源码 如下 希望看到的大神解决了的话帮我一下 RSA a suite of routines for performing RSA public key computations in JavaScript
  • c语言字符串相关函数的分析

    c语言中 常见的字符串相关函数主要分为两类 1 与字符串长度无关的函数 如strcpy strcat strcmp 2 与字符串长度有关的函数 如strlen strncpy strncat strncmp strlen 用于求字符串的长度
  • 1130:找第一个只出现一次的字符(C C++)

    题目描述 给定一个只包含小写字母的字符串 请你找到第一个仅出现一次的字符 如果没有 输出no 输入 一个字符串 长度小于100000 输出 输出第一个仅出现一次的字符 若没有则输出no 输入样例 abcabd 输出样例 c 代码 inclu
  • 【Unity】Delegate, Event, UnityEvent, Action, UnityAction, Func 傻傻分不清

    Unity Delegate Event UnityEvent Action UnityAction Func 傻傻分不清 Delegate 委托 函数指针 一个简单的例子 一对一依赖 一个简单的例子 一对多依赖 所以话说 委托有啥用呢 事
  • LDAP简介及其使用

    LDAP简介 LDAP Lightweight Directory Access Protocol 的意思是 轻量级目录访问协议 是一个用于访问 目录服务器 Directory Servers 的协议 这里所谓的 目录 是指一种按照树状结构
  • java button中加入背景图片不显示

    emmmm 写一下关于在button中添加图片作为背景的经历 就 先记录下错误的地方 JLabel stat new JLabel new ImageIcon img left png 这里再left png的路径的开头少了个点 就一直都不
  • Centos7安装Nessus教程

    本文为学习笔记 仅限学习交流 不得利用 从事危害国家或人民安全 荣誉和利益等活动 请参阅 中华人民共和国网络安全法 Nessus安装包 链接 https pan baidu com s 1FJMu8WMZPSjoqQpes GCng 提取码
  • C++中#ifndef, #define, #endif的作用和使用的注意事项

    在C 语言编程中 我们经常会接触到头文件 比如说声明类 或者声明命名空间等 而每次在编写xxx h的头文件时 编程书上都会让我们在代码的前后加上如下的三句代码 ifndef XXX H define XXX H endif 其中 代表中间具
  • DDP入门

    DDP 即动态动态规划 可以用于解决一类带修改的DP问题 我们从一个比较简单的东西入手 最大子段和 带修改的最大子段和其实是常规问题了 经典的解决方法是用线段树维护从左 右开始的最大子段和和区间最大子段和 然后进行合并 现在我们换一种方法来