JZOJ 5936. 【NOIP2018模拟10.29】逛公园

题解:

从头到尾只会写暴力。

首先要想到一个结论:
f i , j , x f_{i,j,x} fi,j,x表示一开始是x,走i到j这一段后的愉悦度。

显然有:
若x1<=x2,则 f i , j , x 1 &lt; = f i , j , x 2 f_{i,j,x1}&lt;=f_{i,j,x2} fi,j,x1<=fi,j,x2

还有 f i , j , x = m i n ( s i , j , f i , j , ∞ ) f_{i,j,x}=min(s_{i,j},f_{i,j,∞}) fi,j,x=min(si,j,fi,j,)

g i , j = f i , j , ∞ g_{i,j}=f_{i,j,∞} gi,j=fi,j,

然后看数据范围就可以考虑分块了。

对于一个块内的子区间,考虑按s,g排序,把肯定不是答案的去掉,然后对于一组询问就二分。

如果跨块,其实相当于维护一个最大值,搞搞每个块的开头和结尾就行了。

Code:

#include<cstdio> 
#include<algorithm>
#define min(a, b) ((a) < (b) ? (a) : (b))
#define max(a, b) ((a) > (b) ? (a) : (b)) 
#define fo(i, x, y) for(int i = x; i <= y; i ++)
using namespace std;

const int N = 4e4 + 5, M = 150;

int n, q, x, y, z, d[N], L[N], s[N];
int bl[N], l[N], r[N];
int f[M + 5][M + 5];

struct Nod {
	int s, g;
	Nod() {}
	Nod(int _s, int _g) {s = _s, g = _g;}
};
int cmp(Nod a, Nod b) {
	if(a.s > b.s) return 1;
	if(a.s < b.s) return 0;
	return a.g > b.g;
}
struct nod {
	Nod a[M * M + 5], b[M + 5], c[M + 5];
	int a0, b0, c0, h;
	void gg(Nod *a, int &a0) {
		sort(a + 1, a + a0 + 1, cmp); int A = a0; a0 = 0;
		fo(i, 1, A) if(!a0 || a[a0].g < a[i].g) a[++ a0] = a[i];
	}
	void B(int l, int r) {
		fo(i, l, r) {
			f[i - l][i - l] = L[i];
			fo(j, i + 1, r) f[i - l][j - l] = min(L[j], max(f[i - l][j - l - 1], z) + d[j]);
		}
		fo(i, l, r) fo(j, i, r) {
			Nod p = Nod(s[j] - s[i - 1], f[i - l][j - l]);
			a[++ a0] = p; if(i == l) b[++ b0] = p; if(j == r) c[++ c0] = p;
		}
		h = f[0][r - l]; gg(a, a0); gg(b, b0); gg(c, c0);
	}
	int fi(Nod *a, int a0, int z) {
		int A = 0;
		for(int l = 1, r = a0; l <= r; ) {
			int m = l + r >> 1;
			if(a[m].s + z >= a[m].g) A = m, l = m + 1; else r = m - 1;
		}
		return max((!A ? 0 : a[A].g), (A == a0 ? 0 : a[A + 1].s + z));
	}
} a[N / M + 5];
int main() {
	freopen("park.in", "r", stdin);
	freopen("park.out", "w", stdout);
	scanf("%d %d", &n, &q);
	fo(i, 1, n) scanf("%d", &d[i]), s[i] = s[i - 1] + d[i];
	fo(i, 1, n) scanf("%d", &L[i]);
	fo(i, 1, n) {
		bl[i] = bl[i - 1];
		if((i % M) == 1) r[bl[i] ++] = i - 1, l[bl[i]] = i;
	} r[bl[n]] = n;
	fo(i, 1, bl[n]) a[i].B(l[i], r[i]);
	fo(ii, 1, q) {
		scanf("%d %d %d", &x, &y, &z);
		int ans = z;
		if(bl[x] == bl[y]) {
			int v = z;
			fo(i, x, y) v = min(max(v, z) + d[i], L[i]), ans = max(ans, v);
			printf("%d\n", ans);
			continue;
		}
		int v = z;
		fo(i, x, r[bl[x]]) v = min(max(v, z) + d[i], L[i]), ans = max(ans, v);
		fo(i, bl[x] + 1, bl[y] - 1) {
			int V = a[i].fi(a[i].a, a[i].a0, z); ans = max(ans, V);
			V = a[i].fi(a[i].b, a[i].b0, v); ans = max(ans, V);
			v = min(s[r[i]] - s[l[i] - 1] + v, a[i].h);
			V = a[i].fi(a[i].c, a[i].c0, z); v = max(v, V);
		}
		fo(i, l[bl[y]], y) v = min(max(v, z) + d[i], L[i]), ans = max(ans, v);
		printf("%d\n", ans);
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值