CF2126G2 思路分享(单调栈,主席树)

https://codeforces.com/contest/2126/problem/G2

题意

给定长为 \(n\) 的序列 \(a\),求 \(a\) 的子数组中最大的 \(med(a[l\cdots r])-min(a[l\cdots r])\).

其中 \(med(a)\) 表示将 \(a\) 升序排序后第 \(\left\lceil \frac{1+|a|}{2} \right\rceil\) 个元素.

\(1\le n \le 2\cdot 10^5\)\(1\le a_i \le n\).

思路

考虑单调栈,求出每个元素的最小值支配区间,固定最小值.

然后考虑二分中位数 \(mid\),经典地,令 \(a_i \ge mid\)\(a_i\)\(1\),其他为 \(-1\),问题转变成判断支配区间内是否存在包含 \(i\) 的某个区间和 \(\ge 0\).

对于单个 \(mid\),区间和可以由 \((left_i,i]\) 的后缀最大值与 \([i,right_i)\) 的前缀最大值之和表示,考虑用线段树维护.

我们发现,当 \(mid\) 增加的时候,某些位置由 \(1\) 变成 \(-1\),相当于线段树的单点更新,总共更新的次数不会超过 \(n\),考虑用主席树维护所有版本的线段树.

\(mid+1\) 时,可能有多个位置发生更新,记录最后一个更新的线段树版本.

时间复杂度 \(\mathcal{O}(n\log^2 n)\).

代码

//author:kzssCCC

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

const int INF = 1e9;

class node{
public:
	int premx=-INF/2,sufmx=-INF/2;
	int sum = 0;
	int left=-1,right=-1;
};

void solve(){
	int n;
	cin >> n;
	
	vector<int> a(n+1);
	for (int i=1;i<=n;i++){
		cin >> a[i];
	}

	vector<int> left(n+1),right(n+1);
	stack<int> stk;
	for (int i=1;i<=n;i++){
		while (!stk.empty() && a[i]<=a[stk.top()]){
			stk.pop();
		}
		left[i] = stk.empty()?0:stk.top();
		stk.push(i);
	}
	while (!stk.empty()){
		stk.pop();
	}
	for (int i=n;i>=1;i--){
		while (!stk.empty() && a[i]<a[stk.top()]){
			stk.pop();
		}
		right[i] = stk.empty()?n+1:stk.top();
		stk.push(i);
	}

	vector<vector<int>> idx(n+1);
	for (int i=1;i<=n;i++){
		idx[a[i]].push_back(i);
	}	

	vector<node> seg;
	vector<int> root;
	vector<int> last(n+1);

	function<int(int,int)> build = [&](int l,int r)->int{
		if (l==r){
			node temp;
			temp.sum = temp.premx = temp.sufmx = 1;
			seg.push_back(temp);
			return seg.size()-1; 
		}
		
		int mid = l+r >> 1;
		seg.emplace_back();
		int rt = seg.size()-1;
		seg[rt].left = build(l,mid);
		seg[rt].right = build(mid+1,r);

		seg[rt].sum = seg[seg[rt].left].sum+seg[seg[rt].right].sum;
		seg[rt].premx = max(seg[seg[rt].left].premx,seg[seg[rt].left].sum+seg[seg[rt].right].premx);
		seg[rt].sufmx = max(seg[seg[rt].right].sufmx,seg[seg[rt].left].sufmx+seg[seg[rt].right].sum);
		return rt;
	};
	root.push_back(build(1,n));
	last[1] = root.back();

	function<int(int,int,int,int)> update = [&](int rt,int l,int r,int pos){
		if (l==r){
			seg.emplace_back();
			int nrt = seg.size()-1;
			seg[nrt].sum = seg[nrt].premx = seg[nrt].sufmx = -1;
			return nrt;
		}

		int mid = l+r >> 1;
		seg.emplace_back(seg[rt]);
		int nrt = seg.size()-1;

		if (pos<=mid){
			seg[nrt].left = update(seg[rt].left,l,mid,pos);
		}
		else{
			seg[nrt].right = update(seg[rt].right,mid+1,r,pos);
		}

		seg[nrt].sum = seg[seg[nrt].left].sum+seg[seg[nrt].right].sum;
		seg[nrt].premx = max(seg[seg[nrt].left].premx,seg[seg[nrt].left].sum+seg[seg[nrt].right].premx);
		seg[nrt].sufmx = max(seg[seg[nrt].right].sufmx,seg[seg[nrt].left].sufmx+seg[seg[nrt].right].sum);
		return nrt;
	};

	for (int mid=2;mid<=n;mid++){
		for (auto& i:idx[mid-1]){
			root.push_back(update(root.back(),1,n,i));
		}
		last[mid] = root.back();
	}

	function<node(int,int,int,int,int)> query_range = [&](int rt,int l,int r,int x,int y){
		if (r<x || l>y) return node();
		if (x<=l && r<=y){
			return seg[rt];
		}	

		int mid = l+r >> 1;
		node left = query_range(seg[rt].left,l,mid,x,y);
		node right = query_range(seg[rt].right,mid+1,r,x,y);
		node temp;
		temp.sum = left.sum+right.sum;
		temp.premx = max(left.premx,left.sum+right.premx);
		temp.sufmx = max(right.sufmx,left.sufmx+right.sum);
		return temp;
	};

	int res = -INF;
	for (int i=1;i<=n;i++){
		auto check = [&](int mid){
			auto L = query_range(last[mid],1,n,left[i]+1,i);
			auto R = query_range(last[mid],1,n,i,right[i]-1);
			int cur = L.sufmx+R.premx-(a[i]>=mid?1:-1);
			return cur>=0;
		};

		int l=1,r=n;
		while (l<=r){
			int mid = l+r >> 1;
			if (check(mid)){
				l = mid+1;
			}
			else{
				r = mid-1;
			}
		}

		res = max(res,r-a[i]);
	}

	cout << res << '\n';
}

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	
	int t = 1;
	cin >> t;
	while (t--) solve();

	return 0;
}
posted @ 2026-06-24 23:07  kzssCCC  阅读(8)  评论(0)    收藏  举报