「JSOI2016」灯塔(数论分块+ST做RMQ或单调栈)

https://loj.ac/problem/2074

我看到这个题的第一反应是做单调栈:
\(p[i]>=h[j]+\sqrt{|i-j|}-h[i]\)

\(sqrt\)这函数吧,也是单调的,性质应该和直线差不多,所以单调队列维护交点单调的若干条曲线。

求交点可以用二分求,时间复杂度是\(O(n~log~n)\)

有理有据的做法是分块。

考虑\(\sqrt{|i-j|}\)只有\(2\sqrt n\)种取值,且对于每个每个取值,合法的\(j\)在一个区间里,预处理\(ST\)表以快速查询区间最大值最小值。

时间复杂度:\(O(n\sqrt n + n~log~n)\)

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 = 1e5 + 5;

int n, h[N];

struct P {
	ll x, y;
};

#define db long double

ll jd(P a, P b) {
	ll as = 1e18;
	for(ll l = b.x, r = 1e18; l <= r; ) {
		ll m = l + r >> 1;
		db v1 = a.y + sqrt(m - a.x), v2 = b.y + sqrt(m - b.x);
		if(v1 < v2) as = m, r = m - 1; else l = m + 1;
	}
	return as;
}

ll jd2(P a, P b) {
	ll as = -1e18;
	for(ll l = -1e18, r = b.x; l <= r; ) {
		ll m = l + r >> 1;
		db v1 = a.y + sqrt(a.x - m), v2 = b.y + sqrt(b.x - m);
		if(v1 < v2) as = m, l = m + 1; else r = m - 1;
	}
	return as;
}

P z[N]; int st, en;

ll p[N];

int main() {
	scanf("%d", &n);
	fo(i, 1, n) scanf("%d", &h[i]);
	st = 1, en = 0;
	fo(i, 1, n) {
		while(st < en && i >= jd(z[st], z[st + 1])) st ++;
		if(st <= en) {
			p[i] = max(p[i], z[st].y - h[i] + (ll) ceil(sqrt(i - z[st].x)));
		}
		if(st <= en && z[en].y >= h[i]) continue;
		P a = (P) {i, h[i]};
		while(st < en && jd(z[en - 1], z[en]) > jd(z[en], a)) en --;
		z[++ en] = a;
	}
	st = 1, en = 0;
	fd(i, n, 1) {
		while(st < en && i <= jd2(z[st], z[st + 1])) st ++;
		if(st <= en) {
			p[i] = max(p[i], z[st].y - h[i] + (ll) ceil(sqrt(z[st].x - i)));
		}
		if(st <= en && z[en].y >= h[i]) continue;
		P a = (P) {i, h[i]};
		while(st < en && jd2(z[en - 1], z[en]) < jd2(z[en], a)) en --;
		z[++ en] = a;
	}
	fo(i, 1, n) pp("%lld\n", p[i]);
}
```c
posted @ 2020-04-18 22:10  Cold_Chair  阅读(165)  评论(0编辑  收藏  举报