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));
	}
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章