【LG-P2839 [国家集训队]】middle

传送门:洛谷 P2839 [国家集训队]middle

二分求解中位数 + 主席树维护

Solution

1 求中位数

拿到题目首先肯定会去思考怎么求区间中位数。

按照以往求中位数的方法——对顶堆,显然不行,时间肯定会炸。

那就要引入一个新的求中位数的方法了:二分中位数大小,然后将大于等于该数的数的值设为 1,否则设为 -1,然后求区间的和,若大于等于 0,则意味着真正的中位数一定大于等于该数,否则小于该数,时间复杂度同对顶堆一样, O ( n l o g n ) O(nlogn) O(nlogn)

2 线段树维护

这时候我们考虑到题目的另一个限制:左端点在 [ a , b ] [a,b] [a,b] 间,右端点在 [ c , d ] [c,d] [c,d] 间。

唯一能够直接确定的是区间和一定包含 [ b + 1 , c − 1 ] [b+1,c-1] [b+1,c1] 的和。

那么为了让中位数最大,我们就要尽量让区间和最大,所以对于区间 [ a , b ] [a,b] [a,b],我们取和最大的后缀,同理,区间 [ c , d ] [c,d] [c,d] 取和最大的前缀。

确定了如何选取区间内的数,我们考虑如何快速求出它们。

区间求和…不难想到线段树。

再去优化一下,对初始数组进行离散化,那么二分出的中位数的范围就在 1~n 之间。

所以对于每个二分出的 m i d mid mid,我们构造一颗线段树,用来求上述的区间和/区间最大前后缀。

每次 c h e c k ( m i d ) check(mid) check(mid) 的复杂度就变成了 O ( l o g n ) O(logn) O(logn)

3 空间优化——主席树维护

这样一来,空间会炸。

那么同时在多棵线段树上进行空间优化…不难想到主席树。考虑怎样转化为主席树。

我们试着去找离散化后的数组 n u m [ ] num[] num[] 每个位置之间的相同与联系。

我们发现,假如我们已经对当 m i d = n u m [ i ] mid=num[i] mid=num[i] 这种情况按照 2 中所说建立了一颗线段树(比它大(等于)的点权值设为 1,否则设为 -1),那么此时要想再建立一颗 m i d = n u m [ i + 1 ] mid = num[i+1] mid=num[i+1] 这种情况的线段树( i i i i + 1 i+1 i+1 均在离散化后的数组 n u m [ ] num[] num[] 中,即满足单调递增),我们只需要将值为 n u m [ i ] num[i] num[i] 的点在线段树中的权值改为 -1 即可,其余的值不变。

有了上述结论,我们就可以将线段树转化为主席树去优化了。

更详细地,对于 r t i rt_i rti,以它为根节点的线段树内部,值小于 n u m i num_i numi 的点权值为 -1,否则为 1。

那么 r t 1 rt_1 rt1 内部的点的点权没有值为 -1 的情况。

4 总结/思考

重新回顾这道题,发现看到题面首先容易把注意力放在“区间不定”上,但是实际上突破口在于如何求解中位数。在确定怎么求解中位数之后,将“区间不定”转化为“区间确定”也就迎刃而解了。

我们还会发现,随着思路的不断深入,我们先是优化时间复杂度,再是逐渐去优化空间复杂度,但不论优化哪个,都不会是一蹴而就的。

最后,此解法时间复杂度 O ( n l o g n 2 ) O(nlogn^2) O(nlogn2),空间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

5 代码

还有一点处理主席树部分的细节需要见代码注释。

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

#define rep(i, a, b) for(register int i = a; i <= b; ++i)
#define ls t[x].l
#define rs t[x].r
#define s(x) t[x].sum
#define lx(x) t[x].lmx
#define rx(x) t[x].rmx
const int maxn = 2e4 + 5;
int n, m, lst;
int x1, x2, x3, x4;
int ux[15];
int a[maxn], num[maxn], tot;
vector <int> pos[maxn];
int rt[maxn];
struct tree{
	int l, r;
	int sum, lmx, rmx;
	bool el, er;//若左儿子指向的是以前建的节点那么 el=0,否则即新建立了一个左儿子,那么 el=1 
}t[800005];
int cnt, ans;

inline int plc(int x){
	return lower_bound(num + 1, num + tot + 1, x) - num;
}

inline void up(int x){
	s(x) = s(ls) + s(rs);
	lx(x) = max(lx(ls), s(ls) + lx(rs));
	rx(x) = max(rx(rs), s(rs) + rx(ls));
}

inline int build(int l, int r){
	int x = ++cnt;
	if(l == r){
		if(plc(a[l]) <= 1) //是否真的需要这个判断???
			s(x) = lx(x) = rx(x) = 1;
		else 
			s(x) = lx(x) = rx(x) = 1;
		return x;
	}
	int mid = l + r >> 1;
	t[x].el = t[x].er = 1;
	ls = build(l, mid), rs = build(mid + 1, r);
	up(x);
	return x;
}

inline int update(int x, int y, int l, int r, int k, int c, bool f){
	if(!f) x = ++cnt;
	if(l == r){
		s(x) = lx(x) = rx(x) = c;
		return x;
	}
	int mid = l + r >> 1;
	if(k <= mid){
		if(!t[x].er) rs = t[y].r;
		if(!t[x].el)
			t[x].el = 1,
			ls = update(x, t[y].l, l, mid, k, c, 0);
			//如果要修改的节点在左子树,我们要新建一个左子树,而不是直接修改覆盖以前的信息 
		else
			ls = update(ls, t[y].l, l, mid, k, c, 1);
	}
	else{
		if(!t[x].el) ls = t[y].l;
		if(!t[x].er)
			t[x].er = 1,
			rs = update(x, t[y].r, mid + 1, r, k, c, 0);
			//右子树同理 
		else 
			rs = update(rs, t[y].r, mid + 1, r, k, c, 1);
	}
	up(x);
	return x;
}

inline int qy(int x, int l, int r, int L, int R){
	if(l >= L and r <= R) return s(x);
	int mid = l + r >> 1, sm = 0;
	if(L <= mid) sm += qy(ls, l, mid, L, R);
	if(R > mid) sm += qy(rs, mid + 1, r, L, R);
	return sm;
}

inline tree ql(int x, int l, int r, int L, int R){
	if(l >= L and r <= R) return t[x];
	int mid = l + r >> 1;
	if(L <= mid and mid < R){
		tree ret, lt, Rt;
		lt = ql(ls, l, mid, L, R);
		Rt = ql(rs, mid + 1, r, L, R);
		ret.sum = lt.sum + Rt.sum;
		ret.lmx = max(lt.lmx, lt.sum + Rt.lmx);
		return ret;
	}
	else if(L <= mid)
		return ql(ls, l, mid, L, R);
	else
		return ql(rs, mid + 1, r, L, R);
}

inline tree qr(int x, int l, int r, int L, int R){
	if(l >= L and r <= R) return t[x];
	int mid = l + r >> 1;
	if(L <= mid and mid < R){
		tree ret, lt, Rt;
		lt = qr(ls, l, mid, L, R);
		Rt = qr(rs, mid + 1, r, L, R);
		ret.sum = lt.sum + Rt.sum;
		ret.rmx = max(Rt.rmx, Rt.sum + lt.rmx);
		return ret;
	}
	else if(L <= mid)
		return qr(ls, l, mid, L, R);
	else
		return qr(rs, mid + 1, r, L, R);
}

inline bool chck(int md){
	int res = 0;
	if(x2 + 1 <= x3 - 1) 
		res += qy(rt[md], 1, n, x2 + 1, x3 - 1);
	res += qr(rt[md], 1, n, x1, x2).rmx;
	res += ql(rt[md], 1, n, x3, x4).lmx;
	return res >= 0;
}

int main(){
	scanf("%d", &n);
	rep(i, 1, n) 
		scanf("%d", &a[i]), num[++tot] = a[i];
	sort(num + 1, num + tot + 1);
	tot = unique(num + 1, num + tot + 1) - num - 1;
	rep(i, 1, n)
		pos[plc(a[i])].push_back(i);
	rt[1] = build(1, n);
	rep(i, 2, tot)	rep(j, 0, (pos[i - 1].size() - 1))
		rt[i] = update(rt[i], rt[i - 1], 1, n, pos[i - 1][j], -1, rt[i] > 0);
	scanf("%d", &m);
	while(m--){
		scanf("%d%d%d%d", &ux[0], &ux[1], &ux[2], &ux[3]);
		rep(i, 0, 3) ux[i] += lst, ux[i] %= n;
		sort(ux, ux + 4);
		x1 = ux[0] + 1, x2 = ux[1] + 1, x3 = ux[2] + 1, x4 = ux[3] + 1;
		int l = 1, r = tot;
		while(l <= r){
			int mid = l + r >> 1;
			if(chck(mid)) ans = mid, l = mid + 1;
			else r = mid - 1;
		}
		printf("%d\n", lst = num[ans]);
	}
	return 0;
}

—— E n d End End——

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值