首页 > 其他分享 >洛谷题单指南-线段树的进阶用法-P2839 [国家集训队] middle

洛谷题单指南-线段树的进阶用法-P2839 [国家集训队] middle

时间:2025-01-21 15:58:53浏览次数:1  
标签:进阶 P2839 int 洛谷题 sum tr mid ques root

原题链接:https://www.luogu.com.cn/problem/P2839

题意解读:求左端点在 [a,b]之间,右端点在 [c,d]之间的子区间中,最大的中位数。

解题思路:

1、直男暴力法

枚举左、右端点,然后排序计算中位数,这样的复杂度在n*n*logn,显然不可行。

2、渣男巧妙法

首先,要重新来看待何为中位数。

设一段区间的中位数x,将该区间中>=x的数全部置为1,<x的数置为-1,设区间和为sum,如果sum>=0,显然中位数可以更大,反之则更小。

那么,完全可以通过二分最大中位数x(离散化之后的),然后针对每一个x建立一个权值线段树,线段树中节点区间是x对应的原序列下标,节点值>=x的是1,<x的是-1,这样二分时check()函数主要用来判断当前的中位数x是否存在合理的区间满足左端点在 [a,b]之间,右端点在 [c,d]之间。

怎么判断呢?

既然左端点在 [a,b]之间,右端点在 [c,d]之间,那么区间[b+1,c-1]是一定包含的,

区间最大和 = 区间[b+1,c-1]和+区间[a,b]最大后缀和+区间[c,d]最大前缀和

只要区间最大和 >= 0,就可以往更大的数进行二分,反之往更小的数二分。

关于如何计算区间最大后缀和、最小前缀和的方法可以参考:https://www.cnblogs.com/jcwy/p/18582207

如果对于每一个离散化后的中位数都建立一棵线段树,内存显然支撑不住,可以用可持久化线段树进行优化。

将序列中所有数按照从小到大排序,同时记录每个数原下标,从1~n遍历

对于第1个数,建立线段树root[1]的每一个元素值都为1,对于root[i]可以从root[i-1]进行复制,然后将i-1对应的序列原下标的值改为-1。

具体逻辑最好还是参考代码:

100分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 20005, INF = 1e5;

struct Num
{
    int v; //序列的值
    int idx; //在原序列中的下标
} w[N];

bool cmp(Num x, Num y)
{
    return x.v < y.v;
}

struct Node
{
    int L, R;
    int sum; //区间和
    int presum; //区间最大前缀和
    int subsum; //区间最大后缀和
} tr[N * 80];

int root[N], idx;
int ques[4];
int n, q;

void pushup(Node &root, Node &left, Node &right)
{
    root.sum = left.sum + right.sum;
    root.presum = max(left.presum, left.sum + right.presum);
    root.subsum = max(right.subsum, right.sum + left.subsum);
}

void pushup(int u)
{
    pushup(tr[u], tr[tr[u].L], tr[tr[u].R]);
}

//建立root[1]的线段树,所有节点值都为1
int build(int l, int r)
{
    int u = ++idx;
    if(l == r)
    {
        tr[u].sum = tr[u].presum = tr[u].subsum = 1;
        return u;
    }
    int mid = l + r >> 1;
    tr[u].L = build(l, mid);
    tr[u].R = build(mid + 1, r);
    pushup(u);
    return u;
}

//从pre复制线段树,将pos位置的值改为-1
int update(int pre, int l, int r, int pos)
{
    int u = ++idx;
    tr[u] = tr[pre];
    if(l == r)
    {
        tr[u].sum = tr[u].presum = tr[u].subsum = -1;
        return u;
    }
    int mid = l + r >> 1;
    if(pos <= mid) tr[u].L = update(tr[pre].L, l, mid, pos);
    else tr[u].R = update(tr[pre].R, mid + 1, r, pos);
    pushup(u);
    return u;
}

//在根为u的线段树中查询值区间在[lpos,rpos]的sum、presum、subsum
Node query(int u, int l, int r, int lpos, int rpos)
{
    if(lpos > rpos) return {0, 0, 0, -INF, -INF}; //注意边界情况
    if(l >= lpos && r <= rpos) return tr[u];
    else if(l > rpos || r < lpos) return {0, 0, 0, -INF, -INF};
    else 
    {
        int mid = l + r >> 1;
        Node left = query(tr[u].L, l, mid, lpos, rpos);
        Node right = query(tr[u].R, mid + 1, r, lpos, rpos);
        Node res;
        pushup(res, left, right);
        return res;
    }
}

bool check(int a, int b, int c, int d, int x)
{
    //区间最大和
    int sum = query(root[x], 1, n, a, b).subsum + query(root[x], 1, n, b + 1, c - 1).sum + query(root[x], 1, n, c, d).presum;
    return sum >= 0;
}

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i++)
    {
        cin >> w[i].v;
        w[i].idx = i; //记录下标
    }
    sort(w + 1, w + n + 1, cmp); //按值从小到大排序
    root[1] = build(1, n); //建立最小元素的线段树
    //建立可持久化线段树,对于第i个值,把前面一个值的下标对应值变成-1
    for(int i = 2; i <= n; i++) root[i] = update(root[i - 1], 1, n, w[i - 1].idx);
 
    int a, b, c, d, x = 0;
    cin >> q;
    while(q--)
    {
        cin >> a >> b >> c >> d;
        ques[0] = (a + x) % n + 1; //注意原来的序列从0开始,调整为从1开始
        ques[1] = (b + x) % n + 1;
        ques[2] = (c + x) % n + 1;
        ques[3] = (d + x) % n + 1;
        sort(ques, ques + 4);
        int l = 1, r = n, ans;
        while(l <= r)
        {
            int mid = l + r >> 1;
            if(check(ques[0], ques[1], ques[2], ques[3], mid)) 
            {
                ans = mid;
                l = mid + 1;
            }
            else r = mid - 1;
        }
        x = w[ans].v;
        cout << x << endl;
    }

    return 0;
}

 

标签:进阶,P2839,int,洛谷题,sum,tr,mid,ques,root
From: https://www.cnblogs.com/jcwy/p/18675516

相关文章

  • SQL进阶实战技巧:用户会话内行为模式挖掘
    目录0问题描述 1数据准备2问题分析3小结 往期精彩0问题描述分析用户在每个会话内的行为序列,找出最常见的前N种行为模式,并按用户分群。用户表结构和数据假设有名为user_behavior_log的用户行为日志表,包含以下字段:字段名数据类型描述user_idINT用户IDbehav......
  • Markdown转Beamer进阶
    技术背景在前面的一篇文章中,我们介绍过Markdown转Beamer的基本方法。通过这个方案,我们可以只写普通的Markdown文档,甚至可以用Github或者Gitee进行保存和协同编辑。然后在本地环境中通过pandoc进行编译构建,最终可以生成一个Beamer风格的pdf文件。这里我们不讨论到底是用PPT写比较......
  • Python进阶:深入理解import机制与importlib的妙用
    目录一、Pythonimport机制概述1.1import语句的基本用法1.2模块缓存机制1.3导入搜索路径1.4导入钩子和查找器二、importlib的妙用2.1动态模块导入2.2使用importlib实现插件系统2.3重新加载模块三、总结在Python编程的世界里,import语句是开发者最常用的工......
  • JS宏进阶:正则表达式的使用
    正则表达式,对于任何一门编程语言来说,都是一种非常强大的工具,主要用于搜索、编辑或操作文本和数据。因此,在JS中,也存在相应的对象newRegExp(),在本章中,将详细介绍正则表达式在JS宏中的运用。一、正则表达式的创建在基础篇章中,曾提及正则表达式对象,在JS中有两种创建方法,示例如......
  • 【JavaEE进阶】SpringMVC 响应
    目录......
  • Redis 深度解析:从基础到进阶,全面掌握高效缓存技术
    Redis深度解析:从基础到进阶,全面掌握高效缓存技术引言:Redis作为现代开发中不可或缺的技术之一Redis(RemoteDictionaryServer)作为一种开源的高性能键值数据库,在实际开发中发挥着至关重要的作用。它以其极高的读写性能、丰富的数据结构、持久化机制以及支持多种编程语言的客......
  • Tomcat进阶篇
    目录对应的输出结果一、聊聊ClassLoader的那些事儿1.类加载器的过程2.类加载器的分类3.BootstrapClassLoader4.ExtensionClassLoader5.自定义类加载器6.双亲委派机制Catalina为什么不new出来?1.Web容器的特性2.Tomcat类加载器结构3.Tomcat的类加载器的继承结......
  • Flask Web开发实战:入门、进阶与原理解析PDF免费下载
    适读人群:本书适合了解Python基本语法,想要自己动手做网站的编程人员;熟悉Python。想要从事PythonWeb开发的后端工程师、运维工程师和爬虫工程师;香葱Django等其他PythonWeb框架转向Flask的Python工程师阅读。PythonWeb框架Flask开发团队成员撰写,内容全面,从基础知识到进阶实战,再到......
  • 系统编程(进程通信--信号进阶)
    常见问题解决vscode远程连接虚拟机上ubuntu系统,在编写代码时用到的Linux系统函数或者某些常量不提醒或者报红色波浪线的问题:信号的屏蔽和解除信号的屏蔽和解除屏蔽函数的基本使用:#include<stdio.h>#include"header.h"voidhandler(intsignum){pri......
  • Python 进阶 - 多线程(一)
    Python进阶-多线程相关概念解释器GILthreading方法属性threading.enumerate()threading.active_count()threading.current_thread()threading.get_ident()threading.main_thread()threading.stack_size([size])threading.get_native_id()threading.TIMEOUT_MAX线程......