首页 > 其他分享 >需要对某些区间递归处理的线段树的维护

需要对某些区间递归处理的线段树的维护

时间:2022-09-27 21:33:49浏览次数:90  
标签:递归 线段 tr mid len ls 区间 mx

这篇总结来源于本蒟蒻打了两道题目发现了这种类型题,却不知道怎么给它起名字......

对一些已经看出来对区间进行操作和维护,但pushup操作不太容易想出来的题目来说,我们不妨尝试在两区间进行合并时,尝试对左或右区间进行递归,去除不正确的答案,维护区间的正解。

说起来的确很抽象(我甚至也不知道我在说什么),有一个运用这个方法的维护对象----单调栈:

《楼房重建》这道题为例,这题是要维护从第一点开始的斜率单调递增的序列的点的个数(前缀单调递增序列)。

假设要对当前区间s进行pushup,左右子区间需要满足:右区间第一个对答案有贡献的点斜率大于左区间最大的斜率。

因此我们在线段树中维护两个值:最为答案的递增序列长度len,和该区间的最大值max。

首先l == r时,斜率不为0则return 1,否则return 0。

容易发现,s区间的左区间的所有点一定在答案内,因此s区间的len一定是:左区间的len + 右区间的贡献。

需要知道右区间的len不是它的贡献。有可能答案在左区间更新后,右区间的前几个点斜率会比左区间的新答案小。

因此对于右区间,我们把它劈成两个区间:r1和r2,设mx是s左区间的max。

r1和r2有两种情况:

    ①mx大于r1的最大值:此时r1所有的点都不对答案做出贡献,因此我们需要对r2操作。

也就是return pushup(rs, mid + 1, r, mi);

    ②mx小于等于r1的最大值:那么r2的对整个s右区间的贡献就能全部贡献到该区间中,只需要操作r1,

也就是pushup(ls, l, mid, mi) + tr[p].len - tr[ls].len;

这里是tr[p].len - tr[ls].len而不是tr[rs].len是因为,我们一直在递归pushup函数时,并未改变tr[p].len的值(除了递归入口的p),因此r2中还有许多点未必是能贡献到答案中的。这也是本题的,也是递归pushup线段树题目的难点。

 

类似的处理方式在李超线段树中也有过,即当大区间的答案不一定是更大区间的答案,也不一定是小区间的答案,我们需要一直递归下去或者找到绝对的递归出口。

这样的递归处理复杂度为O(logn),它要在每个节点都要进行一次,总体的复杂度就是O(log^2 n)。

贴出丑陋的代码(楼房重建):

#include <bits/stdc++.h>
#define FAST ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
#define debug(x) cout<<endl<<#x<<": "<<x<<endl
#define endl "\n"
#define PII pair<int, int >
#define ls p << 1
#define rs (p << 1) + 1
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
const int N = 1e5 + 10;

struct Segment_Tree {
    int len;
    double mx;
} tr[N << 2];

int n, m;
double k[N];

int pushup(int p, int l, int r, double mi) {
    if(l == r) return k[l] > mi;
    if(tr[p].mx <= mi) return 0;
    if(k[l] > mi) return tr[p].len;
    
    int mid = l + r >> 1;
    if(tr[ls].mx < mi) return pushup(rs, mid + 1, r, mi);
    else return pushup(ls, l, mid, mi) + tr[p].len - tr[ls].len;
}

void edit(int p, int l, int r, int x) {
    if(l == r && l == x) {
        tr[p].len = k[x] != 0;
        tr[p].mx = k[x];
        return ;
    }
    
    int mid = l + r >> 1;
    if(mid >= x) edit(ls, l, mid, x);
    else edit(rs, mid + 1, r, x);
    
    tr[p].mx = max(tr[ls].mx, tr[rs].mx);
    tr[p].len = tr[ls].len + pushup(rs, mid + 1, r, tr[ls].mx);
}

int main() {
    FAST;
    cin>>n>>m;
    
    int id;
    double y;
    while(m--) {
        cin>>id>>y;
        k[id] = (double) y / id;
        edit(1, 1, n, id);
        cout<<tr[1].len<<endl;
    }
    return 0;
}

 

标签:递归,线段,tr,mid,len,ls,区间,mx
From: https://www.cnblogs.com/killerboom/p/16736074.html

相关文章

  • CSP-S模拟13排序 Xorum 有趣的区间问题 无聊的卡牌问题
    T1【构造+规律】:给你一个排列,要你求逆序对数量,把原序列的逆序对位置当成交换,进行任意排列使得最后序列升序。(n<=1000)一:排列的实质是id[i]=i的一一对应,问题互相转化会更简......
  • AcWing 算法提高课 线段树+扫描线法 求矩形之并的面积
    例题:求解多个长方形之并的面积https://www.acwing.com/problem/content/249/蓝色表示长方形,红色表示扫描线如下图所示,对于每一个横向的区间,在纵向维护线段树根据纵向......
  • 递归
    递归概念A方法调用B方法,我们很容易理解!递归就是:A方法调用A方法!就是自己调用自己利用递归可以用简单的程序来解决一些复杂的问题。它通常把一个大型复杂......
  • laravel-admin实现时间搓区间查询
    数据表时间字段使用的时间搓保存,使用查询过滤时发现时间区间查询没有关于时间搓查询的能力,只能是自己实现一个表格查询过滤的between类型,默认是使用控件输入原值作为查......
  • 【luogu CF1109E】Sasha and a Very Easy Test(线段树)
    SashaandaVeryEasyTest题目链接:luoguCF1109E题目大意维护一个长度为n的序列,有区间乘,单点除(保证能整除),区间求和答案对p取模。p不一定是质数。思路麻了考场......
  • 两个和最大的区间(线段树+单调栈+dp)
      胜哥投喂的一道面试题  题意:有一个环形数组\(a\),找出两个不重叠的子串,是的这两个区间内所有的数加起来的和最大。  数据范围:\(1\leqn\leq10^5,\left|......
  • 递归
    1、递归的应用场景递归是一种编程思想。1.在我们日常开发中,如果要遍历一个文件夹下面所有的文件,通常会使用递归来实现;2.很多算法都离不开递归,例如:快速排......
  • 线段树学习笔记(基础&进阶)(一) | P3372 【模板】线段树 1 题解
    什么是线段树线段树是一棵二叉树,每个结点存储需维护的信息,一般用于处理区间最值、区间和等问题。线段树的用处对编号连续的一些点进行修改或者统计操作,修改和统计的复杂......
  • C++实现递归法求1!+2!+3!+…+n!的和
    1#define_CRT_SECURE_NO_WARNINGS2#include<iostream>34usingnamespacestd;5//用递归求某一项的阶乘的值6intfun(inti)//求第i项的值7{8......
  • 斐波那契数列(递归、记忆化搜索、递归)
    题目:菲波那契数列是指这样的数列:数列的第一个和第二个数都为1,接下来每个数都等于前面2个数之和。给出一个正整数k,要求菲波那契数列中第k个数是多少。输入输入一行,......