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/1bfadf00ae14 华为移动服务(Huawei Mobile Services,简称 HMS)是一个全面开放的移动服务生态系统,为企业和开发者提供了丰富的工具和 API,助力他们构建、运营和推广应用。其中,HMS Scankit 是华为推出的一款扫描服务 SDK,支持快速集成到安卓应用中,能够提供高效且稳定的二维码和条形码扫描功能,适用于商品扫码、支付验证、信息获取等多种场景。 集成 HMS Scankit SDK 主要包括以下步骤:首先,在项目的 build.gradle 文件中添加 HMS Core 库和 Scankit 依赖;其次,在 AndroidManifest.xml 文件中添加相机访问和互联网访问权限;然后,在应用程序的 onCreate 方法中调用 HmsClient 进行初始化;接着,可以选择自定义扫描界面或使用 Scankit 提供的默认扫描界面;最后,实现 ScanCallback 接口以处理扫描成功和失败的回调。 HMS Scankit 内部集成了开源的 Zxing(Zebra Crossing)库,这是一个功能强大的条码和二维码处理库,提供了解码、生成、解析等多种功能,既可以单独使用,也可以与其他扫描框架结合使用。在 HMS Scankit 中,Zxing 经过优化,以更好地适应华为设备,从而提升扫描性能。 通常,ScanKitDemoGuide 包含了集成 HMS Scankit 的示例代码,涵盖扫描界面的布局、扫描操作的启动和停止以及扫描结果的处理等内容。开发者可以参考这些代码,快速掌握在自己的应用中实现扫码功能的方法。例如,启动扫描的方法如下: 处理扫描结果的回调如下: HMS Scankit 支持所有安卓手机,但在华为设备上能够提供最佳性能和体验,因为它针对华为硬件进行了
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值