首页 > 其他分享 >线段树优化 dp 学习笔记

线段树优化 dp 学习笔记

时间:2025-01-06 20:59:59浏览次数:6  
标签:tmp limits int 线段 笔记 sum dp

到底是什么算法让我觉得两道题就足以让我写一篇学习笔记呢?

虽然两年半以前写过一道dp,正解的优化是单调队列,但是我拿线段树过了(卡着空间过的),所以那个dp并不能叫线段树优化dp。

CF115E Linear Kingdom Races

这个算是最 “原汁原味” 线段树优化dp。
设 \(dp_{i,j}\) 表示第 \(j\) 条路到第 \(i\) 条路全修的最大收益。(至于第 \(1\) 到 \(j - 1\) 条路,那不是我们该考虑的,这就是dp的精髓)那么有转移:

\[dp_{i,j} = \begin{cases} dp_{i - 1,j} + (\sum\limits_{l_{k} \ge j,r_{k} = i}p_{k}) - c_{i} & j < i \\ (\max\limits_{1 \le y \le x < i}dp_{x,y}) + (\sum\limits_{l_{k} = r_{k} = i}p_{k}) - c_{i} & j = i \\ \end{cases} \]

其中:\(\sum\limits_{l_{k} \ge j,r_{k} = i}p_{k}\) 和 \(\sum\limits_{l_{k} = r_{k} = i}p_{k}\) 分别表示右端点在 \(i\) 且被区间 \([i,j]\) 覆盖的的比赛收益之和 和 左端点和右端点都在 \(i\) 的比赛收益之和。
那为何在 \(j < i\) 时只用考虑 \(l_{k} \ge j,r_{k} = i\) 的比赛呢?因为 \(r_{k} < i\) 的比赛都在 \(dp_{i - 1,?}\) 考虑过了,而 \(r_{k} > i\) 的不是现在该考虑的。
但是,这题不可能这么简单,单是存状态和枚举状态就可以让我们爆炸了
先来优化空间:我们发现:\(\max\limits_{1 \le y \le x < i}dp_{x,y}\) 就是对于 \(i - 1\) 的答案,于是第二个方程就可以表示成 \(ans + (\sum\limits_{l_{k} = r_{k} = i}p_{k}) - c_{i}\) 。而我们发现第一个方程又只会从 \(i - 1\) 来转移,那么我们可以考虑什么?滚动数组啊。那么空间就优化完了。
现在来优化时间。我们设每一个 \(dp_{i,j}\) 都有一个待定值数组 \(tmp\) ,那么 \(dp_{i,j}\) 一定由 \(tmp\) 里的某一个值转移而来。对于暴力来说,就是每次新枚举一个新的状态,都给他一个新的 \(tmp\) 数组,但是我们要优化,我们就不可能每次用 \(\mathcal{O}(n)\) 给他一个新的 \(tmp\) 数组,我们应该继承上一个状态的 \(tmp\) 数组,并操作它使它变成当前状态的 \(tmp\) 数组。我们发现,每次转移,\(tmp\) 数组的 \([1,i]\) 项都会加上 \(c_{i}\) ,也会加上 \(\sum\limits_{l_{k} \ge j,r_{k} = i}p_{k}\) 。这不就是区间操作吗?我们就可以用线段树来维护这个值。线段树里装的就是待定值数组 \(tmp\) 。而对于 \(i = j\) 的情况,因为本来只有 \([1,i - 1]\) 的下标里有值,而我们要用到 \(tmp_{i}\) 的值,我们就可以给他赋值一个 \(ans\) ,正如同方程里的一样,而题目里让我们求最大的收益,那么线段树查询的就是区间 \([1,i]\) 里的最大值。这样,我们就做完了。
有些细节比如如何记录 \(r_{k} = i\) 的比赛会在代码里说明。

#include<bits/extc++.h>
#define int long long
#define ls (rt << 1)
#define rs (rt << 1 | 1)
using namespace std;
const int maxn = 2e5 + 5;
int n,m;
int a[maxn];
struct edge
{
    int l,p;
    edge *nxt;
}*head[maxn];
struct Nahida//不要关心这个名字
{
    int l,r;
    int val,lazy;
}tree[maxn << 2];
void push_up(int rt){tree[rt].val = max(tree[ls].val,tree[rs].val);}
void push_down(int rt)
{
    if (!tree[rt].lazy)
        return;
    tree[ls].val += tree[rt].lazy;
    tree[rs].val += tree[rt].lazy;
    tree[ls].lazy += tree[rt].lazy;
    tree[rs].lazy += tree[rt].lazy;
    tree[rt].lazy = 0;
}
void build(int l,int r,int rt)
{
    tree[rt].l = l;
    tree[rt].r = r;
    if (l == r)
    {
        tree[rt].val = tree[rt].lazy = 0;
        return;
    }
    int mid = (l + r) >> 1;
    build(l,mid,ls);
    build(mid + 1,r,rs);
}
void upd(int ql,int qr,int x,int rt = 1)
{
    int l = tree[rt].l;
    int r = tree[rt].r;
    if (ql <= l && r <= qr)
    {
        tree[rt].val += x;
        tree[rt].lazy += x;
        return;
    }
    push_down(rt);
    int mid = (l + r) >> 1;
    if (ql <= mid)
        upd(ql,qr,x,ls);
    if (qr > mid)
        upd(ql,qr,x,rs);
    push_up(rt);
}
void adde(int l,int r,int p)
{
    auto tmp = new edge;
    tmp->l = l;
    tmp->p = p;
    tmp->nxt = head[r];
    head[r] = tmp;
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    int l,r,p;
    for (int i = 1; i <= m; i++)
    {
        cin >> l >> r >> p;
        adde(l,r,p);//用链式前向星(邻接表)记录右端点为i的比赛。
    }
    build(1,n,1);
    int ans = 0;
    for (int i = 1; i <= n; i++)//滚动数组滚掉一维,就可以存下了。
    {//这里没有重新建树就是继承了上一个的tmp数组
        upd(i,i,ans);//单点修改,增添一个ans
        upd(1,i,-a[i]);//上面说的:区间更改
        for (auto j = head[i]; j; j = j->nxt)
            upd(1,j->l,j->p);//也是区间修改
        ans = max(ans,tree[1].val);//记录答案
    }
    cout << ans;
    return 0;
}

好题推荐:

P2605 基站选址

标签:tmp,limits,int,线段,笔记,sum,dp
From: https://www.cnblogs.com/Eous/p/18656285

相关文章

  • HTML 个人笔记
    介绍HTML(hypertextmarkuplangage)超文本标记语言主要用于描述网页的结构和内容学习HTML主要学习HTML标签以及标签对应的属性,标签就是一些特殊的符号比如img图片标签(属性src,width,height,alt,tilte),在网页中显示图片a超链接标签(属性href,target),实现网页的跳转......
  • CSS 个人笔记
    介绍HTML(hypertextmarkuplangage)超文本标记语言主要用于描述网页的结构和内容学习HTML主要学习HTML标签以及标签对应的属性,标签就是一些特殊的符号比如img图片标签(属性src,width,height,alt,tilte),在网页中显示图片a超链接标签(属性href,target),实现网页的跳转......
  • c#笔记(2)
    c#程序是一组类型声明C#程序或DLL的源代码是一组类型声明,这些类型声明定义了程序或库的结构、行为以及如何与外部环境交互命名空间是一种将相关类型声明分组并命名的方法,因此通常在作者创造的命名空间内部声明程序类型c#program(1)c#程序由一个或多个类型声明组成。Console......
  • c# 笔记(1)
    理论扩展:win32apic++使用Windows操作系统的基础编程接口32window的基石不能面对对象.dllMFC微软基础类库MFC以c++封装了windowapi得以面向对象开发window程序.dllCOM的英文全称是,ComponentObjectModel组件对象模型 .dll优点:在任意两个组件之间进行通信不用考......
  • PySpark学习笔记2-RDD算子,RDD持久化
    RDD定义RDD是弹性分布式数据集,是spark中的最基本的数据抽象,里面的元素可以并行计算RDD的五大特性RDD是有分区的,它的分区是数据存储的最小单位RDD的方法会作用在所有分区上RDD之间是有依赖关系的KV型的RDD可以有分区器RDD的分区会尽量靠近数据所在的服务器,尽量保证本......
  • [数据结构学习笔记6] 树(Trees)
    为什么要有树结构,我们日常生活中,有很多层级关系,比如家庭树,组织架构图等等。这些或许也能够用数组或者链表来表示,但是这个比喻很好,就好像我们用叉子和盘子也能喝汤,但总是不对劲儿,我们可以有更好的表示方式。 了解树的一些术语树是由一系列节点(node)和边(edge)相互关联构成的。孩......
  • 二项式反演和容斥原理学习笔记
    容斥原理先从容斥原理开始。容斥原理的结论如下:$$|\bigcup\limits_{i=1}^{n}S_{i}|=\sum\limits_{m=1}{n}(-1)\sum\limits_{a_{i}<a_{i-1}}|\bigcap_{i=1}^{m}S_{a_{i}}|$$证明的思路是考虑一个元素在每一个$\bigcap\limits_{i=1}^{m}S_{a_{i}}$里出现的次......
  • E94 Tarjan边双缩点+树形DP P8867 [NOIP2022] 建造军营
    视频链接:E94Tarjan边双缩点+树形DPP8867[NOIP2022]建造军营_哔哩哔哩_bilibili  P8867[NOIP2022]建造军营-洛谷|计算机科学教育新生态//Tarjan边双缩点+树形DPO(n)#include<bits/stdc++.h>usingnamespacestd;intread(){intx=0,f=1;charc=getchar......
  • 零基础手把手教您在笔记本电脑上部署kubernetes 1.31.3 集群
    我是知识的实践者,关注我,手把手教您部署实践。贵在真实,主打就是一个按步骤做下去不会出错。一、Kubernetes概述一句话,他很强大,你必须要学会,否则就跟不上技术的潮流了。本人资源有限,在笔记本电脑上16G内存部署k8s集群,让大家都能接触到新技术NAMESTATUSROLES......
  • 电脑盲如何选购笔记本避免被坑?很简单,不用别人教你也能搞定.240108
     经常有人问伟哥,笔记本电脑该如何选购。其实很简单,一是看预算,二是到某东看预算范围内的热销电脑,闭眼选就是。笔记本现在也是消费品了,没那么多讲究。 当然,如果要显得你自己更加专业或更加有逼格一点,那,在笔记本选购中,要注意两点:CPU和硬盘。 硬盘很好说,现在这时代,一定是固态......