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入门 的相关文章

  • 《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 题目概述 解析 参考答案 注意事项 说明 题目概述 算法说明 输入一颗二叉树的根节点和一个整数 按字典序打印出二叉树中结点值的和为输入整数的所有路径 路径定义为从树的根结点开始往下一直到叶结点所经
  • BP神经网络与Python实现

    人工神经网络是一种经典的机器学习模型 随着深度学习的发展神经网络模型日益完善 联想大家熟悉的回归问题 神经网络模型实际上是根据训练样本创造出一个多维输入多维输出的函数 并使用该函数进行预测 网络的训练过程即为调节该函数参数提高预测精度的过程
  • LeetCode83: 删除排序链表中的重复元素

    给定一个已排序的链表的头 head 删除所有重复的元素 使每个元素只出现一次 返回 已排序的链表 示例 1 输入 head 1 1 2 输出 1 2 示例 2 输入 head 1 1 2 3 3 输出 1 2 3 提示 链表中节点数目在范围
  • 数据结构之链表与线性表

    数据结构之链表与线性表 线性表 顺序线性表 顺序表 顺序线性表 使用数组实现 一组地址连续的存储单元 数组大小有两种方式指定 一是静态分配 二是动态扩展 优点 随机访问特性 查找O 1 时间 存储密度高 逻辑上相邻的元素 物理上也相邻 缺点
  • 『Python基础-15』递归函数 Recursion Function

    什么是递归函数 一种计算过程 如果其中每一步都要用到前一步或前几步的结果 称为递归的 用递归过程定义的函数 称为递归函数 例如连加 连乘及阶乘等 凡是递归的函数 都是可计算的 即能行的 递归就是一个函数在它的函数体内调用它自身 编程语言中的
  • 数据结构与算法书籍推荐

    学习数据结构与算法 还是很有必要看几本相关的书籍 但根据不同基础的人 合适看的书也不一样 因此 针对不同层次 不同语言的人 推荐几本市面上口碑不错的书 1 入门级 针对刚入门的同学 建议不要急着去看那些经典书 像 算法导论 算法 这些比较经
  • 图 - Java实现无向带权图的邻接矩阵表示法

    图 Java实现无向带权图的邻接矩阵表示法 1 图 1 1 图的介绍 图 Graph 是一种复杂的非线性表结构 图中的元素我们就叫做顶点 vertex 图中的一个顶点可以与任意其他顶点建立连接关系 我们把这种建立的关系叫做边 edge 跟顶
  • 堆栈01--用两个栈实现队列

    堆栈01 用两个栈实现队列 jz05 题目概述 解析 参考答案 注意事项 说明 题目概述 算法说明 用两个栈来实现一个队列 完成队列的Push和Pop操作 队列中的元素为int类型 测试用例 队列先进先出 输入 1 2 输出 1 2 解析
  • 算法问题实战策略

    算法问题实战策略 基本信息作者 韩 具宗万 译者 崔盛一出版社 人民邮电出版社ISBN 9787115384621上架时间 2015 2 4出版日期 2015 年3月开本 16开页码 738版次 1 1 内容简介 算法问题实战策略 本书收录
  • 算法学习——贪心算法之币种统计

    算法描述 币种统计 单位给每一位员工发工资 精确到元 为了保证不临时换零钱 使得每个员工取款的张数最少 在取工资前统计所有员工所需要的各种票面的张数 约定票种为100 50 20 10 5 2 1元 并验证币种统计是否正确 算法思路 算法描
  • 用指针访问一维数组

    文章目录 顺序查找 数组方式实现 指针实现方式 对一位数组元素的访问有三种方式 指针变量的关系运算 引例 数组实现方式 主函数 指针实现方式 主函数 一维数组作为函数的参数 实际应用 顺序查找 要求用指针实现 在整数集合r中顺序查找与给定值
  • 时间复杂度+常见复杂度解释

    前言 算法的效率 虽然计算机能快速的完成运算处理 但实际上 它也需要根据输入数据的大小和算法效率来消耗一定的处理器资源 要想编写出能高效运行的程序 我们就需要考虑到算法的效率 算法的效率主要由以下两个复杂度来评估 时间复杂度 评估执行程序所
  • 机器学习算法GBDT的面试要点总结-上篇

    1 简介 gbdt全称梯度提升决策树 在传统机器学习算法里面是对真实分布拟合的最好的几种算法之一 在前几年深度学习还没有大行其道之前 gbdt在各种竞赛是大放异彩 原因大概有几个 一是效果确实挺不错 二是即可以用于分类也可以用于回归 三是可
  • Linux下进程退出的几种形式

    进程退出 Linux 下进程的退出分为正常退出和异常退出两种 1 正常退出 a 在main 函数中执行return b 调用exit 函数 c 调用 exit 函数 2 异常退出 a 调用about函数 b 进程收到某个信号 而该信号使程序
  • Leetcode2661. 找出叠涂元素

    Every day a Leetcode 题目来源 2661 找出叠涂元素 解法1 哈希 题目很绕 理解题意后就很简单 由于矩阵 mat 中每一个元素都不同 并且都在数组 arr 中 所以首先我们用一个哈希表 hash 来存储 mat 中每
  • 牛客剑指offer刷题其他算法篇

    文章目录 构建乘积数组 题目 思路 代码实现 第一个只出现一次的字符
  • 【数据结构】双链表的定义和操作

    目录 1 双链表的定义 2 双链表的创建和初始化 3 双链表的插入节点操作 4 双链表的删除节点操作 5 双链表的查找节点操作 6 双链表的更新节点操作 7 完整代码 嗨 我是 Filotimo 很高兴与大家相识 希望我的博客能对你有所帮助
  • C++ AVL树(四种旋转,插入)

    C AVL树 四种旋转 插入 一 AVL树的概念及性质 二 我们要实现的大致框架 1 AVL树的节点定义 2 AVL树的大致框架 三 插入 1 插入逻辑跟BST相同的那一部分 2 修改平衡因子

随机推荐

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

    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问题 我们从一个比较简单的东西入手 最大子段和 带修改的最大子段和其实是常规问题了 经典的解决方法是用线段树维护从左 右开始的最大子段和和区间最大子段和 然后进行合并 现在我们换一种方法来