Luogu P2839 [国家集训队]middle___二分+主席树

该博客详细介绍了如何使用主席树和线段树数据结构来高效解决寻找区间中位数并满足特定条件的问题。通过建立不同版本的线段树,实现了在离散化后的数组中快速查询满足条件的最大中位数,同时给出了具体的算法实现过程和代码示例。

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

题目大意:


在这里插入图片描述

分析:

考虑一个合法的答案ddd
一定满足区间内>=d区间内>=d>=d的数的个数多于<d<d<d的个数,设差值为xxx,即 x>=0x>=0x>=0
找到最大ddd满足x>=0x>=0x>=0dmaxd_{max}dmax即最大中位数
由于答案只可能是a1,a2..,ana1,a2..,ana1,a2..,an
考虑离散后对于每个可能的答案建立一个主席树
假设bbbaaa去重后的升序序列
bib_ibi为中位数,将>=bi>=b_i>=biaaa记作+1,<bi<b_i<bi的记作-1,
那么对于[a,b],[c,d][a,b],[c,d][a,b],[c,d]是否合法,
就是xmax=[a,b]最大后缀和+[b+1,c−1]总和+[c,d]最大前缀和x_{max}=[a,b]最大后缀和+[b+1,c-1]总和+[c,d]最大前缀和xmax=[a,b]+[b+1,c1]+[c,d]
是否满足xmax>=0x_{max}>=0xmax>=0
可以用线段树维护中位数为bib_ibi时,序列的区间和,区间最大前缀和,区间最大后缀和
考虑
中位数bib_ibi ->bi+1b_{i+1}bi+1,
发现变化只有aaa中等于bib_ibi的数,由+1+1+1变成−1-11
直接用邻接表存起来这些位置,暴力改,然后以bib_ibi为历史版本线段树,
建立当前版本的线段树,将改过的点更新,因为当前版本线段树有多次加点加新链,所以注意点的重复的判断
b1b_1b1为初始版本的线段树,
b2,b3,b4,...,bnb_2,b_3,b_4,...,b_nb2,b3,b4,...,bn依次建成新的版本线段树即可
因为aaa中每个数最多被枚举1次,
所以时间是 O(nlog2n)O(nlog_2n)O(nlog2n)

对于一个询问,
二分bbb的下标,判断对应版本是否符合xmax>=0x_{max}>=0xmax>=0,判断直接找当前版本的树即可
时间是O(log22n)O(log_2^2n)O(log22n)

代码:

#include<bits/stdc++.h>

#define N 20005

using namespace std;


struct Node {
	int ls, rs, sum, qian, hou;
}T[N*20];

int rot[N], nxt[N], pod[N];
int a[N], b[N], id[N], n, m, cnt;
int p[5];

void merge(int x) {
	T[x].sum = T[T[x].ls].sum+T[T[x].rs].sum;
	T[x].qian = max(T[T[x].ls].qian, T[T[x].ls].sum+T[T[x].rs].qian);
	T[x].hou = max(T[T[x].rs].hou, T[T[x].rs].sum+T[T[x].ls].hou);
}

int Build(int L, int R) {
	int now = ++cnt;
	if (L == R) {
		T[now].qian = T[now].hou = T[now].sum = 1;
		return now;
	}
	int mid = (L+R)>>1;
	if (L < R) {
		T[now].ls = Build(L, mid);
		T[now].rs = Build(mid+1, R);
	}
	merge(now);
	return now;
}

void insert(int now2, int now1, int pos) {
	int L = 1, R = n;
	for (; T[now1].ls || T[now1].rs ;) {
		int mid = (L+R)>>1;
		if (pos <= mid) {
			if (!T[now2].rs) T[now2].rs = T[now1].rs; 
			if (!T[now2].ls || T[now2].ls == T[now1].ls) T[now2].ls = ++cnt; 
			now1 = T[now1].ls;
			now2 = T[now2].ls;
			R = mid;
		} else {
			if (!T[now2].ls) T[now2].ls = T[now1].ls; 
			if (!T[now2].rs || T[now2].rs == T[now1].rs) T[now2].rs = ++cnt;
			now1 = T[now1].rs;
			now2 = T[now2].rs;
			L = mid+1;
		}
 	}	
}

void update(int now, int L, int R, int pos) {
	if (L == R) { T[now].sum = T[now].qian = T[now].hou = -1; return; }
	int mid = (L+R)>>1;
	if (pos <= mid) update(T[now].ls, L, mid, pos); else update(T[now].rs, mid+1, R, pos);
	merge(now);
}

int Getmax(int now, int L, int R, int liml, int limr, int opt) {
	if (L == liml && R == limr) {
		if (opt == 1) return T[now].qian; 
		if (opt == 2) return T[now].hou; 
		if (opt == 3) return T[now].sum;
	}
	int mid = (L+R)>>1;
	if (liml > mid) return Getmax(T[now].rs, mid+1, R, liml, limr, opt);
	  else if (limr <= mid) return Getmax(T[now].ls, L, mid, liml, limr, opt);
	          else {
	          	    if (opt == 1) return max(Getmax(T[now].ls, L, mid, liml, mid, 1), 
					    Getmax(T[now].ls, L, mid, liml, mid, 3)+Getmax(T[now].rs, mid+1, R, mid+1, limr, 1));
			        if (opt == 2) return max(Getmax(T[now].rs, mid+1, R, mid+1, limr, 2),
					    Getmax(T[now].rs, mid+1, R, mid+1, limr, 3)+Getmax(T[now].ls, L, mid, liml, mid, 2));
			        if (opt == 3) return Getmax(T[now].ls, L, mid, liml, mid, 3)+Getmax(T[now].rs, mid+1, R, mid+1, limr, 3);
			  }
}

bool check(int now) {
	now = rot[now];
	int cmax1 = Getmax(now, 1, n, p[1], p[2], 2);
	int cmax2 = Getmax(now, 1, n, p[3], p[4], 1);
	int sum = 0;
	if (p[2]+1 <= p[3]-1) sum = Getmax(now, 1, n, p[2]+1, p[3]-1, 3);
	return (sum+cmax1+cmax2>=0);
}

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) scanf("%d", &a[i]), b[i] = a[i];
	sort(b+1, b+n+1);
	m = unique(b+1, b+n+1)-b-1;
	for (int i = 1; i <= n; i++) id[i] = lower_bound(b+1, b+m+1, a[i])-b;
	for (int i = n; i >= 1; i--) nxt[i] = pod[id[i]], pod[id[i]] = i;
	rot[1] = Build(1, n);
	for (int i = 2; i <= m; i++) {
		rot[i] = ++cnt;
		for (int j = pod[i-1]; j; j = nxt[j]) insert(rot[i], rot[i-1], j);
		for (int j = pod[i-1]; j; j = nxt[j]) update(rot[i], 1, n, j);
	} 
	int asknum; 
	scanf("%d", &asknum);
	int L, R, ans = 0;
	for (int ask = 1; ask <= asknum; ask++) {
		for (int i = 1; i <= 4; i++) scanf("%d", &p[i]), p[i] = (p[i]+ans)%n;
		sort(p+1, p+4+1);
		for (int i = 1; i <= 4; i++) ++p[i]; 
		L = 1, R = m; ans = 0; 
		while (L <= R) {
			int mid = (L+R)>>1; 
			if (check(mid)) ans = b[mid], L = mid+1; else R = mid-1; 
		}
		printf("%d\n", ans);
	}
	return 0;	
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值