【洛谷2839/BZOJ2653】middle(主席树)

题目:

洛谷2839

分析:

s i s_i si表示原序列中第 i i i大的数。
考虑对于任意一个区间 [ a , b ] [a,b] [a,b],设它的中位数为 s m s_m sm,那么这个区间内大于等于 s m s_m sm的数和小于 s m s_m sm的数的数量要么相等,要么小于比大于等于多一个。后一种情况当且仅当 s m ∈ [ a , b ] s_m\in [a,b] sm[a,b]且序列长度为奇数。
考虑如果已知一个数 s i s_i si,如何判断是否存在区间 [ e , f ] [e,f] [e,f] e ∈ [ a , b ] , f ∈ [ c , d ] e\in [a,b],f\in [c,d] e[a,b],f[c,d])使 [ e , f ] [e,f] [e,f]的中位数大于等于 s i s_i si呢(显然满足条件的 i i i是单调的,可以二分)?
对于 s i s_i si,记所有小于它的数为 − 1 -1 1,大于等于它的数为 1 1 1,那么如果能找到一个符合条件的区间 [ e , f ] [e,f] [e,f]使这段区间的和大于等于 0 0 0说明最大中位数大于等于 s i s_i si,记录答案并尝试更大的 i i i;否则不符合条件,需要调小 i i i
此时问题变成了已知一个 1 1 1 − 1 -1 1组成的序列,求一个 [ e , f ] [e,f] [e,f]使它的区间和最大。把 [ e , f ] [e,f] [e,f]拆成三部分:必选的 ( b , c ) (b,c) (b,c),在 [ a , b ] [a,b] [a,b]中选一段后缀的 [ e , b ] [e,b] [e,b]和在 [ c , d ] [c,d] [c,d]中选一段前缀的 [ c , f ] [c,f] [c,f]。这个可以用线段树解决,方法类似【Vijos1083/BZOJ1756】小白逛公园(线段树),查询 ( b , c ) (b,c) (b,c) v a l val val [ a , b ] [a,b] [a,b] r m rm rm [ c , d ] [c,d] [c,d] l m lm lm
s i s_i si对应的线段树和 s i − 1 s_{i-1} si1对应的线段树的区别仅仅是把 ( i − 1 ) (i-1) (i1)位置的 1 1 1变成了 − 1 -1 1,因此可以用主席树维护节省空间。

代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
namespace zyt
{
	const int N = 2e4 + 10, Q = 2.5e4 + 10;
	int n, q, tmp[N], head[N];
	pair<int, int> arr[N];
	namespace Chairman_Tree
	{
		const int SUM = 0, SUML = 1, SUMR = 2;
		struct node
		{
			int sum, suml, sumr, s[2];
			node(): sum(0), suml(0), sumr(0)
			{
				s[0] = s[1] = 0;
			}
		}tree[N * 20];
		int cnt;
		inline void update(node &now, const node &lt, const node &rt)
		{
			now.suml = now.sumr = 0;
			now.sum = lt.sum + rt.sum;
			now.suml = max(lt.suml, lt.sum + rt.suml);
			now.sumr = max(rt.sumr, rt.sum + lt.sumr);
		}
		inline void update(const int rot)
		{
			update(tree[rot], tree[tree[rot].s[0]], tree[tree[rot].s[1]]);
		}
		int build(const int lt, const int rt, const int val)
		{
			int rot = ++cnt;
			if (lt == rt)
			{
				tree[rot].sum = tree[rot].suml = tree[rot].sumr = -1 + 2 * (tmp[lt] >= val);
				return rot;
			}
			int mid = (lt + rt) >> 1;
			tree[rot].s[0] = build(lt, mid, val);
			tree[rot].s[1] = build(mid + 1, rt, val);
			update(rot);
			return rot;
		}
		int change(const int pre, const int lt, const int rt, const int pos, const int x)
		{
			int rot = ++cnt;
			tree[rot] = tree[pre];
			if (lt == rt)
			{
				tree[rot].sum = x;
				tree[rot].suml = x;
				tree[rot].sumr = x;
				return rot;
			}
			int mid = (lt + rt) >> 1;
			if (pos <= mid)
				tree[rot].s[0] = change(tree[pre].s[0], lt, mid, pos, x);
			else
				tree[rot].s[1] = change(tree[pre].s[1], mid + 1, rt, pos, x);
			update(rot);
			return rot;
		}
		node query(const int rot, const int lt, const int rt, const int ls, const int rs)
		{
			if (ls <= lt && rt <= rs)
				return tree[rot];
			int mid = (lt + rt) >> 1;
			if (rs <= mid)
				return query(tree[rot].s[0], lt, mid, ls, rs);
			else if (ls > mid)
				return query(tree[rot].s[1], mid + 1, rt, ls, rs);
			else
			{
				node tmp[2] = {query(tree[rot].s[0], lt, mid, ls, rs),
				   				query(tree[rot].s[1], mid + 1, rt, ls, rs)};
				node ans;
				update(ans, tmp[0], tmp[1]);
				return ans;
			}
		}
		int query(const int rot, const int ls, const int rs, const int type)
		{
			node ans = query(rot, 0, n - 1, ls, rs);
			switch (type)
			{
			case SUM:
				return ans.sum;
			case SUML:
				return ans.suml;
			case SUMR:
				return ans.sumr;
			}
		}
	}
	int work()
	{
		using namespace Chairman_Tree;
		ios::sync_with_stdio(false);
		cin.tie(0);
		cin >> n;
		for (int i = 0; i < n; i++)
			cin >> arr[i].first, arr[i].second = i, tmp[i] = arr[i].first;
		sort(arr, arr + n);
		head[0] = build(0, n - 1, arr[0].first);
		for (int i = 1; i < n; i++)
			head[i] = change(head[i - 1], 0, n - 1, arr[i - 1].second, -1);
		cin >> q;
		int x = 0;
		while (q--)
		{
			int in[4];
			for (int i = 0; i < 4; i++)
				cin >> in[i], in[i] = (in[i] + x) % n;
			sort(in, in + 4);
			int l = 0, r = n - 1, ans;
			while (l <= r)
			{
				int mid = (l + r) >> 1;
				int tmp = query(head[mid], in[0], in[1], SUMR) + query(head[mid], in[2], in[3], SUML);
				if (in[1] + 1 <= in[2] - 1)
					tmp += query(head[mid], in[1] + 1, in[2] - 1, SUM);
				if (tmp >= 0)
					l = mid + 1, ans = mid;
				else
					r = mid - 1;
			}
			cout << arr[ans].first << '\n';
			x = arr[ans].first;
		}
		return 0;
	}
}
int main()
{
	return zyt::work();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值