首页 > 其他分享 >动态dp

动态dp

时间:2024-08-19 20:26:52浏览次数:9  
标签:cur int max dfn INF 动态 dp

T1

一共 \(n\) 颗糖果,第 \(i\) 颗的价值为 \(a[i]\),你不能连着选两颗,请问你的选到的最大价值为多少
显然有如下写法 :
设 \(dp[i][0/1]\)表示选到了第 \(i\) 颗,第 \(i\) 颗选或不选
显然有转移 :
\(dp[i][0] = max(dp[i - 1][0], dp[i - 1][1])\)
\(dp[i][1] = max(dp[i - 1][0] + a[i], dp[i][1] - INF)\)
那么可以列出一下矩阵 :
\([0, 0]\)
\([a[i], -INF]\)
在考虑 \(i + 1\) 的转移 :
\(dp[i + 1][0] = max(dp[i][0], dp[i][1])\)
\(dp[i + 1][1] = max(dp[i][0] + a[i + 1], dp[i][1] - INF)\)
将两个转移结合可得 :
\(dp[i + 1][0] = max(dp[i - 1][0], dp[i - 1][0], dp[i - 1][0] + a[i], dp[i][1] - INF)\)
\(dp[i + 1][1] = max(dp[i - 1][0] + a[i + 1], dp[i - 1][1] + a[i + 1], dp[i - 1][0] + a[i] - INF, dp[i][1] - INF - INF)\)
简化得 :
如果将
\([a, b]\)
\([c, d]\)

\([e, f]\)
\([g, h]\)
合并,可得出下矩阵
\([max(a + e, b + g), max(a + f, b + h)]\)
\([max(c + e, d + g), max(c + f, d + h)]\)
我们来考虑如果需要修改如何计算, 即:
给定 \(q\) 个查询,每次将 \(a[p]\) 改为 \(x\),请输出你选到的最大价值
我们只需要将这些 \(2 \times 2\) 的矩阵缩进一个线段树里,线段树的合并操作按照前面矩阵的合并操作合并即可,下面为合并操作的代码

for (int a : {0, 1}) {
  for (int b : {0, 1}) {
    for (int c : {0, 1}) {
      tr[i][a][b] = max(tr[i][a][b], tr[i * 2][a][c] + tr[i * 2 + 1][c][b]);
    }
  }
}

Count Paths Queries

我们可以记录 \(g[i][j][k]\) 表示从 \(i\) 至 \(j\), 最多走了 \(2 ^ k\) 步的最大价值
那么显然这个数组有单调性,倍增考虑即可

#include <bits/stdc++.h>

using namespace std;

const int N = 2e2 + 5, mod = 1e9 + 7;

int n, m, q, g[31][N][N], dp[N], tmp[N];

int main() {
  ios::sync_with_stdio(0);
  cin.tie(0);
  cin >> n >> m >> q;
  for (int i = 1, u, v; i <= m; i++) {
    cin >> u >> v;
    g[0][u][v] = 1;
  }
  for (int i = 1; i <= 30; i++) {
    for (int a = 1; a <= n; a++) {
      for (int b = 1; b <= n; b++) {
        for (int c = 1; c <= n; c++) {
          g[i][a][b] = (g[i][a][b] + 1ll * g[i - 1][a][c] * g[i - 1][c][b]) % mod;
        }
      }
    }
  }
  while (q--) {
    int s, t, k;
    cin >> s >> t >> k;
    fill(dp + 1, dp + n + 1, 0);
    dp[s] = 1;
    for (int i = 30; i >= 0; i--) {
      if (k >= (1 << i)) {
        k -= (1 << i);
        for (int j = 1; j <= n; j++) {
          tmp[j] = dp[j];
          dp[j] = 0;
        }
        for (int j = 1; j <= n; j++) {
          for (int k = 1; k <= n; k++) {
            dp[k] = (dp[k] + 1ll * tmp[j] * g[i][j][k]) % mod;
          }
        }
      }
    }
    cout << dp[t] << "\n";
  }
  return 0;
}

洛谷P4719

我们显然可以列出一个树形 \(dp\) :

void dfs(int u, int f) {
  dp[u][1] = a[u];
  fa[u] = f;
  for (auto v : g[u]) {
    if (v == f) {
      continue;
    }
    dfs(v, u);
    dp[u][0] += max(dp[v][1], dp[v][0]);
    dp[u][1] += dp[v][0];
  }
}

但是,如果每次查询都从 \(x\) 开始,跑到根节点,那么时间复杂度来到了 \(O(n \times m)\)而序列 \(dp\) 又不能在树上进行,咋办呢?只需要将树剖成一条一条链,然后在链与链的交点处特别处理一下 \(dp\) 即可,如图 :
image
那么 \(dp[u][0] += max(dp[v][1], dp[v][0])\), \(dp[u][1] += dp[v][0]\)
剩下的就是本片博客的 T1 部分

#include <bits/stdc++.h>

using namespace std;

#define int long long

const int N = 1e5 + 5, INF = 2e18;

int n, m, a[N], dp[N][2];

int dcnt, dfn[N], sz[N], top[N], fa[N], son[N], tail[N];

struct node {
  int a[2][2];
}tr[N * 4];

vector<int> g[N];

node Merge(const node &l, const node &r) {
  node tmp = {-INF, -INF, -INF, -INF};
  for (int i : {0, 1}) {
    for (int j : {0, 1}) {
      for (int k : {0, 1}) {
        tmp.a[i][j] = max(tmp.a[i][j], l.a[i][k] + r.a[k][j]);
      }
    }
  }
  return tmp;
}

node query(int i, int l, int r, int x, int y) {
  if (l >= x && r <= y) {
    return tr[i];
  }
  int mid = (l + r) >> 1;
  if (x <= mid && y > mid) {
    return Merge(query(i * 2, l, mid, x, y), query(i * 2 + 1, mid + 1, r, x, y));
  }
  if (x <= mid) {
    return query(i * 2, l, mid, x, y);
  }
  if (y > mid) {
    return query(i * 2 + 1, mid + 1, r, x, y);
  }
  return {-INF, -INF, -INF, -INF};
}

void modify(int i, int l, int r, int p, const node &cur) {
  if (l == r) {
    tr[i] = cur;
    return ;
  }
  int mid = (l + r) >> 1;
  if (mid >= p) modify(i * 2, l, mid, p, cur);
  else modify(i * 2 + 1, mid + 1, r, p, cur);
  tr[i] = Merge(tr[i * 2], tr[i * 2 + 1]);
}

void dfs1(int u, int f) {
  fa[u] = f;
  sz[u] = 1;
  for (auto v : g[u]) {
    if (v == f) {
      continue;
    }
    dfs1(v, u);
    sz[u] += sz[v];
    if (sz[son[u]] < sz[v]) {
      son[u] = v;
    }
  }
}

void dfs2(int u, int f) {
  dfn[u] = ++dcnt;
  if (son[u]) {
    top[son[u]] = top[u];
    dfs2(son[u], u);
  }
  else tail[top[u]] = u;
  for (auto v : g[u]) {
    if (v == son[u] || v == f) {
      continue;
    }
    top[v] = v;
    dfs2(v, u);
    auto cur = query(1, 1, n, dfn[v], dfn[tail[v]]);
    dp[u][1] += cur.a[0][0];
    dp[u][0] += max(cur.a[1][0], cur.a[0][0]);
  }
  modify(1, 1, n, dfn[u], {dp[u][0], dp[u][0], dp[u][1] + a[u], -INF});
}

signed main() {
  cin >> n >> m;
  for (int i = 1; i <= n; i++) {
    cin >> a[i];
  }
  for (int i = 1, u, v; i < n; i++) {
    cin >> u >> v;
    g[u].push_back(v);
    g[v].push_back(u);
  }
  dfs1(1, 0);
  top[1] = 1;
  dfs2(1, 0);
  while (m--) {
    int p, x;
    cin >> p >> x;
    a[p] = x;
    while (p) {//对 p 至他的链头做修改
      int tp = top[p];
      auto cur = query(1, 1, n, dfn[tp], dfn[tail[tp]]);
      dp[fa[tp]][1] -= cur.a[0][0];
      dp[fa[tp]][0] -= max(cur.a[0][0], cur.a[1][0]);
      modify(1, 1, n, dfn[p], {dp[p][0], dp[p][0], dp[p][1] + a[p], -INF});
      cur = query(1, 1, n, dfn[tp], dfn[tail[tp]]);
      dp[fa[tp]][1] += cur.a[0][0];
      dp[fa[tp]][0] += max(cur.a[0][0], cur.a[1][0]);
      p = fa[tp];
    }
    auto cur = query(1, 1, n, dfn[1], dfn[tail[1]]);
    cout << max(cur.a[0][0], cur.a[1][0]) << "\n";
  }
  return 0;
}
/*

标签:cur,int,max,dfn,INF,动态,dp
From: https://www.cnblogs.com/libohan/p/18368052

相关文章

  • 动态规划:找出每个位置为止最长的有效障碍赛跑路线
    目录问题定义思路解题过程复杂度code问题定义        你打算构建一些障碍赛跑路线。给你一个 下标从0开始 的整数数组 obstacles ,数组长度为 n ,其中 obstacles[i] 表示第 i 个障碍的高度。对于每个介于 0 和 n-1 之间(包含 0 和 n-1)的下......
  • 动态dp & 矩阵加速递推
    广义矩阵乘法我们定义两个矩阵\(A,B\)在广义矩阵乘法下的乘积为\(C\),其中\[C=\begin{bmatrix}\max\limits_{i=1}\limits^{m}A_{1,i}+B_{i,1}&\max\limits_{i=1}\limits^{m}A_{1,i}+B_{i,2}&\dots&\max\limits_{i=1}\limits^{m}A_{1,i}+B_{i,k}\\\......
  • tcp与udp的总结+connect阻塞+tcp三次握手、四次挥手+常见的服务器IO(发送数据+接收数
    一,TCP与UDP的基本总结TCP(传输控制协议)和UDP(用户数据报协议)是两种主要的传输层协议。TCP是面向连接的,提供可靠、顺序的传输,适用于需要高可靠性的应用,如网页浏览和文件传输。它通过重传机制和流量控制确保数据完整性。UDP是无连接的,速度快但不保证数据的可靠性和顺序,适用于对实时性......
  • C/C++语言基础--指针三大专题详解2(指针与数组关系,动态内存分配,代码均可)
    本专栏目的更新C/C++的基础语法,包括C++的一些新特性前言指针是C/C++的灵魂,和内存地址相关联,运行的时候速度快,但是同时也有很多细节和规范要注意的,毕竟内存泄漏是很恐怖的指针打算分三篇文章进行讲解,本专题是二,介绍了指针和数组的关系、动态内存如何分配和释放等问题专题......
  • [oeasy]python0030_动态控制断点_breakpoints_debug_调试
     030_动态控制断点_breakpoints_debug_调试290播放·0赞同视频​设置断点_break_point_continue_运行到断点......
  • 动态表单后端设计
     动态表单通常用于收集各种不同类型的数据,这些数据可能随时间变化或根据用户的需求而变化。因此,数据库设计和接口设计需要足够灵活以适应不同的表单结构。以下是一些关于动态表单的数据库设计和接口设计的基本指导原则:数据库设计表单元数据表:form_id(表单ID)form_name(表......
  • 内存(动态开辟)———C语言
    内存管理: 1.C语言运行时的内存分配2.static关键字1.修饰变量局部变量:        <1>在编译的过程中,会在数据区为该变量开辟空间,如果代码中未对其进行初始化,则系统默认初始化为0。        <2>用static修饰的局部变量,会延长局部变量的生命周期#include<s......
  • 预设性DP
    预设性DP从最简单的起DP搬运工2Description给你n,Kn,Kn,K,求有多少个111到nnn的排列,恰好有KKK个数i(1<i<n)i(1<i<n)i(1<i<n)满足ai−1,ai+1a_{i-1},a_{i+1}ai−1​,ai+1​都小于aia_iai​。InputFormat一行两个整数n,Kn,Kn,K。OutputFormat一行一个整数......
  • 【面试】阐述TCP和UDP的区别
    面试模拟场景面试官:你能阐述一下TCP和UDP的区别吗?###参考回答示例1.连接性TCP:面向连接(Connection-Oriented):TCP是一种面向连接的协议,在传输数据之前需要建立连接。在TCP通信过程中,客户端和服务器之间通过“三次握手”来建立连接,然后再进行数据传输,确保两者之间的......
  • 基于django+vue框架的实时新闻推送平台edpjq【开题报告+程序+论文】计算机毕设
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容研究背景在信息爆炸的时代,新闻资讯的时效性成为了媒体竞争的关键。随着互联网技术的飞速发展,人们获取新闻的方式已从传统的报纸、电视转向了手机、......