bzoj2653middle 主席树+二分答案

本文介绍了一种解决复杂查询问题的方法,通过主席树维护序列的相对大小,并结合二分查找与动态规划思想,实现对特定区间内序列的最大中位数查询。此方法巧妙地利用1和-1表示序列中元素的相对大小关系,通过线段树进行区间最大字段和的计算。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

2653: middle

Time Limit: 20 Sec   Memory Limit: 512 MB
Submit: 1855   Solved: 1031
[ Submit][ Status][ Discuss]

Description

一个长度为n的序列a,设其排过序之后为b,其中位数定义为b[n/2],其中a,b从0开始标号,除法取下整。给你一个
长度为n的序列s。回答Q个这样的询问:s的左端点在[a,b]之间,右端点在[c,d]之间的子序列中,最大的中位数。
其中a<b<c<d。位置也从0开始标号。我会使用一些方式强制你在线。

Input

第一行序列长度n。接下来n行按顺序给出a中的数。
接下来一行Q。然后Q行每行a,b,c,d,我们令上个询问的答案是
x(如果这是第一个询问则x=0)。
令数组q={(a+x)%n,(b+x)%n,(c+x)%n,(d+x)%n}。
将q从小到大排序之后,令真正的
要询问的a=q[0],b=q[1],c=q[2],d=q[3]。  
输入保证满足条件。
第一行所谓“排过序”指的是从大到小排序!

Output

Q行依次给出询问的答案。

Sample Input

5
170337785
271451044
22430280
969056313
206452321
3
3 1 0 2
2 3 1 4
3 1 4 0

271451044
271451044
969056313

Sample Output

HINT

  0:n,Q<=100

1,...,5:n<=2000

0,...,19:n<=20000,Q<=25000


Source


clj的题目果然难啊。。。膜拜各种题解之后写出来的
如果只是求某区间中位数的话考虑可以使用主席树维护权值然后bst一下
然而多了个最大,所以就要二分答案(sb没有想到,基础不够扎实)
二分答案x后如果是定区间只需要验证方案的合法,而动区间则需要构造函数来判断这个值是否可以增大。
显然中位数是相对大小的产物,因此用1代表大于等于x,-1代表小于x,那么对与某个区间的一段转换为1和-1的子区间,如果它的和大于0,显然中位数要更大。很经典的转化方法。
问题到目前为止转化为:给定一个序列,序列中的数均是1或-1,求起始下标为[a,b],终止下标为[c,d]所有子序列中最大的子序列和是否大于0。这显然是一个最大字段和问题。
发现(b,c)是必取的,而最大化的目标可以转化为已b为终止下标在区间[a,b]上的最大字段和和以c为起始下标在区间[c,d]上的最大子段和。这显然对应的是线段树中维护左子区间最大和右子区间最大的做法。
最后一个问题就是,对于不同的x如何维护它对应的线段树。我们发现,如果x转移到x的后继,那么只有一个值x会发生变化。这样就可以直接套用主席树模板。具体来说:先把初始每个数设置为1,然后从小到大吧每个数插入一颗以位置主席树中并将其改成-1
整理一下做法和思路:对于最大的中位数,先构造根据每个数在原区间上的相对大小的序列,及大于等于这个数就为1,否则为-1,这个用主席树维护,然后二分答案x,判断以x为中位数可大还是可小,在主席树上找(b,c)和rm[a,b]和lm[c,d]相加,若大于零则x可能增大,小于零则x必须减小。

一道高级数据结构里蕴含了动态规划与构造性的思想,实在是一道难得的好题。

/**************************************************************
    Problem: 2653
    User: 2014lvzelong
    Language: C++
    Result: Accepted
    Time:2264 ms
    Memory:14168 kb
****************************************************************/
 
#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
#include<cmath>
#define maxn 32000
#define inf 0x3f3f3f3f
using namespace std;
int read()
{
    char ch = getchar(); int x = 0, f = 1;
    while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
    return x * f;
}
struct data {
    int val, id;
    bool operator < (const data &a) const {
        return val == a.val ? id < a.id : val < a.val;
    }
}a[maxn];
int n, m, sz, root[maxn];
int sum[maxn * 20], rs[maxn * 20], ls[maxn * 20], lm[maxn * 20], rm[maxn * 20];
 
void update(int p) {
    sum[p] = sum[ls[p]] + sum[rs[p]];
    lm[p] = max(lm[ls[p]], sum[ls[p]] + lm[rs[p]]);
    rm[p] = max(rm[rs[p]], sum[rs[p]] + rm[ls[p]]);
}
 
void build(int &p, int L, int R) {
    p = ++sz;
    if(L == R) {
        sum[p] = lm[p] = rm[p] = 1;
        return;
    }
    int mid = L + R >> 1;
    build(ls[p], L, mid);
    build(rs[p], mid + 1, R);
    update(p);
}
 
void add_tree(int &cur_p, int pre_p, int pos, int L, int R, int val) {
    cur_p = ++sz;
    if(L == R) {
        sum[cur_p] = lm[cur_p] = rm[cur_p] = val;
        return;
    }
    int mid = L + R >> 1;
    if(pos <= mid) {
        rs[cur_p] = rs[pre_p];
        add_tree(ls[cur_p], ls[pre_p], pos, L, mid, val);
    }
    else {
        ls[cur_p] = ls[pre_p];
        add_tree(rs[cur_p], rs[pre_p], pos, mid + 1, R, val);
    }
    update(cur_p);
}
 
void init() {
    n = read();
    for(int i = 0;i < n; ++i) {
        scanf("%d", &a[i].val);
        a[i].id = i;
    }
    sort(a, a + n); lm[0] = rm[0] = -inf;
    build(root[0], 0, n - 1);
    for(int i = 1;i < n; ++i) add_tree(root[i], root[i - 1], a[i - 1].id, 0, n - 1, -1);
}
 
int query_sum(int p, int st, int ed, int L, int R) {
    if(st > ed) return 0;
    if(st == L && ed == R) return sum[p];
    int ans = 0, mid = L + R >> 1;
    if(st <= mid) ans += query_sum(ls[p], st, min(mid, ed), L, mid);
    if(ed > mid) ans += query_sum(rs[p], max(mid + 1, st), ed, mid + 1, R);
    return ans;
}
 
int query_lm(int p, int st, int ed, int L, int R) {
    if(st == L && ed == R) return lm[p];
    int ans = -inf, mid = L + R >> 1;
    if(st <= mid) ans = max(ans, query_lm(ls[p], st, min(ed, mid), L, mid));
    if(ed > mid) ans = max(ans, query_sum(ls[p], st, mid, L, mid) + query_lm(rs[p], max(mid + 1, st), ed, mid + 1, R));
    return ans;
}
 
int query_rm(int p, int st, int ed, int L, int R) {
    if(st == L && ed == R) return rm[p];
    int ans = -inf, mid = L + R >> 1;
    if(ed > mid) ans = max(ans, query_rm(rs[p], max(mid + 1, st), ed, mid + 1, R));
    if(st <= mid) ans = max(ans, query_sum(rs[p], mid + 1, ed, mid + 1, R) + query_rm(ls[p], st, min(ed, mid), L, mid));
    return ans;
}
 
bool judge(int cur, int a, int b, int c, int d) {
    int ret = query_sum(root[cur], b + 1, c - 1, 0, n - 1);
    ret += query_rm(root[cur], a, b, 0, n - 1);
    ret += query_lm(root[cur], c, d, 0, n - 1);
    return ret >= 0;
}
 
void solve() {
    m = read(); int ans = 0, q[4];
    while(m--) {
        for(int i = 0;i < 4; ++i) q[i] = (read() + ans) % n;
        sort(q, q + 4);
        int L = 0, R = n - 1;
        while(L < R) {
            int mid = L + R + 1 >> 1;
            if(judge(mid, q[0], q[1], q[2], q[3])) L = mid;
            else R = mid - 1;
        }
        printf("%d\n", ans = a[L].val);
    }
}
 
int main()
{
    init();
    solve(); 
    return 0;
}


资源下载链接为: https://pan.quark.cn/s/22ca96b7bd39 在当今的软件开发领域,自动化构建与发布是提升开发效率和项目质量的关键环节。Jenkins Pipeline作为一种强大的自动化工具,能够有效助力Java项目的快速构建、测试及部署。本文将详细介绍如何利用Jenkins Pipeline实现Java项目的自动化构建与发布。 Jenkins Pipeline简介 Jenkins Pipeline是运行在Jenkins上的一套工作流框架,它将原本分散在单个或多个节点上独立运行的任务串联起来,实现复杂流程的编排与可视化。它是Jenkins 2.X的核心特性之一,推动了Jenkins从持续集成(CI)向持续交付(CD)及DevOps的转变。 创建Pipeline项目 要使用Jenkins Pipeline自动化构建发布Java项目,首先需要创建Pipeline项目。具体步骤如下: 登录Jenkins,点击“新建项”,选择“Pipeline”。 输入项目名称和描述,点击“确定”。 在Pipeline脚本中定义项目字典、发版脚本和预发布脚本。 编写Pipeline脚本 Pipeline脚本是Jenkins Pipeline的核心,用于定义自动化构建和发布的流程。以下是一个简单的Pipeline脚本示例: 在上述脚本中,定义了四个阶段:Checkout、Build、Push package和Deploy/Rollback。每个阶段都可以根据实际需求进行配置和调整。 通过Jenkins Pipeline自动化构建发布Java项目,可以显著提升开发效率和项目质量。借助Pipeline,我们能够轻松实现自动化构建、测试和部署,从而提高项目的整体质量和可靠性。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值