2019牛客暑期多校训练营(第七场)E.Find the median(权值线段树 + 区间离散化)

题目链接

题意:这题就是先让你套个公式把n个区间算出来,然后对于单个区间在序列里加上这个区间内所有数,然后再求此时序列的中位数。

题解:中位数实际上就是求整个序列第n/2大,考虑权值线段树,但是因为区间范围太大,所以要对区间进行离散化。

离散化之前,首先要将所有区间的左端点减1。为什么呢?因为离散化后线段树上的每个点存的实际上是一段区间,那么比如区间【3,8】,离散化之前我们要把它变成(2,8】,这样我的3这个点才能代表(2,3】这个区间。

给定两个区间我们来模拟一下 【3,8】,【5,9】 ,离散化后是这样子的

2 4 8 9
1 2 3 4

此时(2,8】 对应的是 【1,3】,(4,9】对应的是【2,4】;

还记得我之前说过的吗,线段树上每一个点代表的是一段区间。

这个体现在哪里,体现在对于每一个区间我都会用s[r] - s[l - 1] 来表示,详情请看代码。

那么在线段树上【1,1】这个点代表的是 s[1] - s[0](0,2】;

【2,2】这个点代表的是s[2] - s[1](2,4】,依次类推下去 3代表的是(4,8】,4代表的是(8,9】。

所以当我要插入【3,8】时,对应的离散化区间不应该是【1,3】,而应该是【1 + 1,3】也就是【2,3】,因为【2,2】覆盖了(2,4】,【3,3】覆盖了(4,8】,所以左端点要往后移一个位置。

这里离散化之前和之后的区间混在一起可能有点绕,不过我觉得我已经讲得很清楚了,如果还有不懂的地方欢迎提问。

#include<bits/stdc++.h>
using namespace std; 
#define mid (l + r) / 2
#define ls o * 2
#define rs o * 2 + 1
typedef long long ll;
const int N = 8e5 + 10;
ll sum[N * 4], lazy[N * 4];
ll x[N], y[N], L[N], R[N], s[N];
int n, cnt;
void init()
{
	ll a1,b1,c1,m1;
	ll a2,b2,c2,m2;
	cin>>n;
	cin>>x[1]>>x[2]>>a1>>b1>>c1>>m1;
	cin>>y[1]>>y[2]>>a2>>b2>>c2>>m2;
	for(int i = 1; i <= n; ++i){
		if(i > 2){
			x[i] = (a1 * x[i - 1] % m1 + b1 * x[i - 2] % m1 + c1 % m1) % m1;
			y[i] = (a2 * y[i - 1] % m2 + b2 * y[i - 2] % m2 + c2 % m2) % m2;
		}
		L[i] = min(x[i], y[i]);//左区间-1 
		R[i] = max(x[i], y[i]) + 1;
		s[++cnt] = L[i];
		s[++cnt] = R[i];
	}
	sort(s + 1, s + 1 + cnt);
	cnt = unique(s + 1, s + 1 + cnt) - s - 1;
	for(int i = 1; i <= n; ++i){
		L[i] = lower_bound(s + 1, s + 1 + cnt, L[i]) - s; 
		R[i] = lower_bound(s + 1, s + 1 + cnt, R[i]) - s; 
	}
}
void pu(int o, int l, int r){
	if(lazy[o]){
		lazy[ls] += lazy[o];
		lazy[rs] += lazy[o];
		sum[ls] += (s[mid] - s[l - 1]) * lazy[o];
		sum[rs] += (s[r] - s[mid]) * lazy[o];
		lazy[o] = 0;
	} 
} 
void up(int o, int l, int r, int ql, int qr){
	if(ql <= l && qr >= r){
		sum[o] += s[r] - s[l - 1];
		lazy[o]++;
		return;
	}
	pu(o, l, r);
	if(ql <= mid) up(ls, l, mid, ql, qr);
	if(qr > mid) up(rs, mid + 1, r, ql, qr);
	sum[o] = sum[ls] + sum[rs];
}
ll qu(int o, int l, int r, ll val){
	if(l == r){
		int tmp = sum[o] / (s[r] - s[l - 1]);//求这段区间内每个数的个数 
		return (val + tmp - 1) / tmp + s[l - 1];//第一个式子就是向上取整一下 
	}
	pu(o, l, r);
	if(val <= sum[ls]) return qu(ls, l, mid, val);//权值线段树求区间第k大 
	else return qu(rs, mid + 1, r, val - sum[ls]);
}
int main()
{
	init();
	for(int i = 1; i <= n; ++i){
		up(1, 1, cnt, L[i] + 1, R[i]);//左区间+1 
		ll v = sum[1]/2 + (sum[1] % 2 == 1);//中位数所在位置 
		printf("%lld\n", qu(1, 1, cnt, v));
	}
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值