JZOJ 6188【NOI2019模拟2019.5.30】Duliu

博客围绕区间查询问题给出题解。先提出O(nlog2n)方法,通过找性质将有用区间缩成O(n)个,即笛卡尔树。对于查询区间[l,r]分成三种情况,前两种可二分查询,第三种是三维偏序,经证明后可使用二维偏序数据结构处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在这里插入图片描述
1<=n,m<=3e5

题解:

O(nlog2n)O(n log^2 n)O(nlog2n)的方法很好想。

一个log主要是找性质。

首先思考一个区间[l,r]
如果a[l-1]<=max(l,r),则可以扩展到[l-1,r]
右端同理

这样的话有用的区间就被缩成了O(n)个,就是每个点开始往左往右一直走直到大于,也就是笛卡尔树。

现在假设查询[l,r]

考虑分成三种区间。
1.x=l的
2.y=r的
3.x>l && y<r的

前两种可以直接二分查询。

第三种看上去还是个三维偏序

假设第1、2问搞出的区间是
[l,tl]和[tr,r]

则只用查询y∈[tl…r]或x∈[x…tr]的区间就好了(都要查)

只证明第一个:
假设是区间[x,y],y一定要>=tl,不然由第一位没解得。
那么为什么不用管x<l的情况呢,
首先当x<l时,value(x,y)一定>=z(因为value(l,tl)>=z,[l…tl]∈[x…y])
其次这个区间一定是能扩展就扩展。所以有max(x,y)==max(l,y)

第二个同理,所以随便写个二维偏序的数据结构即可。

Code:

#include<bits/stdc++.h>
#define fo(i, x, y) for(int i = x, B = y; i <= B; i ++)
#define ff(i, x, y) for(int i = x, B = y; i <  B; i ++)
#define fd(i, x, y) for(int i = x, B = y; i >= B; i --)
#define ll long long
#define pp printf
#define hh pp("\n")
using namespace std;

const int N = 3e5 + 5;

int n, m, x, y; ll z;
int a[N], d[N];
int g[19][N];

int cmp(int x, int y) {
	if(a[x] == a[y]) return x < y;
	return a[x] < a[y];
}

int f[N], mi[N], mx[N];

int F(int x) { return f[x] == x ? x : (f[x] = F(f[x]));}

void bin(int x, int y) {
	if(F(x) != F(y)) {
		x = f[x], y = f[y];
		mi[y] = min(mi[y], mi[x]);
		mx[y] = max(mx[y], mx[x]);
		f[x] = y;
	}
}

struct P {
	int x, y, v;
	ll z;
} e[N]; int e0;

int query(int x, int y) {	
	if(!x || !y) return 1e9;
	int l = log2(y - x + 1);
	return max(g[l][x], g[l][y - (1 << l) + 1]);
}

ll s[N];

ll calc(int x, int y) { return (s[y] - s[x - 1]) * (y - x + 1) * 2; }

int cmpe(P x, P y) {
	return x.z > y.z;
}

struct tree {
	int l, r, v;
} t[N * 40];
int g1[N], g2[N], tt;
int pl, pr, px;
#define i0 t[i].l
#define i1 t[i].r
void add(int &i, int x, int y) {
	if(y < pl || x > pr) return;
	t[++ tt] = t[i]; i = tt;
	if(x == y) {
		t[i].v = min(t[i].v, px);
		return;
	}
	int m = x + y >> 1;
	add(i0, x, m); add(i1, m + 1, y);
	t[i].v = min(t[i0].v, t[i1].v);
}
void ft(int i, int x, int y) {
	if(y < pl || x > pr || !i) return;
	if(x >= pl && y <= pr) {
		px = min(px, t[i].v);
		return;
	}
	int m = x + y >> 1;
	ft(i0, x, m); ft(i1, m + 1, y);
}

int main() {
	freopen("duliu.in", "r", stdin);
	freopen("duliu.out", "w", stdout);
	scanf("%d %d", &n, &m);
	fo(i, 1, n) scanf("%d", &a[i]);
	
	
	fo(i, 1, n) g[0][i] = a[i];
	fo(j, 1, 18) fo(i, 1, n) {
		g[j][i] = g[j - 1][i];
		if(i + (1 << j - 1) <= n) g[j][i] = max(g[j][i], g[j - 1][i + (1 << j - 1)]);
	}
	fo(i, 1, n)	s[i] = s[i - 1] + a[i];
	
	fo(i, 1, n) d[i] = i;
	sort(d + 1, d + n + 1, cmp);
	fo(i, 1, n)	f[i] = mi[i] = mx[i] = i;
	fo(i, 1, n) {
		int x = d[i];
		if(x > 1 && a[x - 1] <= a[x]) bin(x - 1, x);
		if(x < n && a[x + 1] <= a[x]) bin(x + 1, x);
		e[++ e0].x = mi[F(x)];
		e[e0].y = mx[F(x)];
	}
	fo(i, 1, e0) {
		e[i].z = calc(e[i].x, e[i].y);
		e[i].v = query(e[i].x, e[i].y);
	}
	sort(e + 1, e + e0 + 1, cmpe);
	t[0].v = 1e9;
	fo(i, 1, e0) {
		g1[i] = g1[i - 1];
		g2[i] = g2[i - 1];
		
		pl = pr = e[i].x; px = e[i].v;
		add(g1[i], 1, n);
		
		pl = pr = e[i].y;
		add(g2[i], 1, n);

	}
	fo(i, 1, m) {
		scanf("%d %d %lld", &x, &y, &z);
		int as1 = 0;
		for(int l = x, r = y; l <= r; ) {
			int mi = l + r >> 1;
			if(calc(x, mi) >= z) as1 = mi, r = mi - 1; else l = mi + 1;
		}
		int as2 = 0;
		for(int l = x, r = y; l <= r; ) {
			int mi = l + r >> 1;
			if(calc(mi, y) >= z) as2 = mi, l = mi + 1; else r = mi - 1;
		}
		int as3 = 0;
		for(int l = 1, r = e0; l <= r; ) {
			int mi = l + r >> 1;
			if(e[mi].z >= z) as3 = mi, l = mi + 1; else r = mi - 1;
		}
		px = min(query(x, as1), query(as2, y));
		if(as1) {
			pl = as1, pr = y;
			ft(g2[as3], 1, n);
		}
		if(as2) {
			pl = x, pr = as2;
			ft(g1[as3], 1, n);
		}
		pp("%d\n", px > 1e7 ? -1 : px);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值