【luogu CF1039E】Summer Oenothera Exhibition(贪心)(根号分治)(LCT)

41 篇文章 0 订阅
13 篇文章 0 订阅

Summer Oenothera Exhibition

题目链接:luogu CF1039E

题目大意

给你一个数组,然后每次问你一个 k,问你能把数组最少分成多少段,使得每一段的极差不超过 k。

思路

首先考虑对于一次询问怎么做。

考虑到一个贪心的想法,就是一个区间能往右扩一点是一点。
不难发现这样是正确的。

然后你考虑是否可以有一些东西优化这个过程。
那你每个地方你可以通过二分+区间求极差【可以用 ST 表】得到它作为一段的左边的话下一个段应该从哪里开始 n x t i nxt_i nxti

然后你会发现如果我们把它连边,它可以相当于要求从 1 1 1 n + 1 n+1 n+1 点的路径长度。
那这个加边的我们可以用 DP 一下得到。
那考虑有没有一些东西方便扩展的,那 k k k 修改了边可能会变,那我们也可以用 LCT 来维护。

但是你每次可能都要更改每个边的 n x t i nxt_i nxti 导致修改次数是 n 2 n^2 n2 复杂度是 n 2 log ⁡ n n^2\log n n2logn 甚至不如暴力。
那我们考虑根号分治。

你会发现暴力跳的问题就是每次跳的距离太短,LCT 跳的问题就是修改的次数太多了。
那我们 ⩽ B \leqslant B B 的我们用 LCT,那这样每个数的 n x t i nxt_i nxti 顶多修改 B B B 次。
然后大于的用暴力做,那每次这么跳的次数是 n B \dfrac{n}{B} Bn
B = n B=\sqrt{n} B=n 就可以做到 O ( n n log ⁡ n ) O(n\sqrt{n}\log n) O(nn logn)

然后接着简单讲讲实现,考虑记录每个每个位置记录它是怎么跳的。
然后 LCT 的部分就不说了,说说怎么修改 n x t i nxt_i nxti
考虑算出每个位置下一次修改是在那个询问,然后放进这个询问的 vector 里面。
然后每次看询问之间先把 vector 里面的搞出来修改了。

然后更新就是暴力加出位置,然后再用 lower_bound 得到下次在哪个询问。

代码

#include<cmath>
#include<cstdio>
#include<vector>
#include<iostream>
#include<algorithm>

using namespace std;

const int N = 1e5 + 100;
const int B = 230;
struct node {
	int id, k;
}s[N];
int n, w, q, a[N], log2_[N], nxt[N];
int maxn[N][18], minn[N][18], xl[N];
int ans[N];
vector <int> st[N];
bool bl[N];

bool cmp(node x, node y) {
	return x.k < y.k;
}

int clac(int l, int r) {
	if (r == n + 1) return 2e9; 
	int k = log2_[r - l + 1];
	return max(maxn[l][k], maxn[r - (1 << k) + 1][k]) - min(minn[l][k], minn[r - (1 << k) + 1][k]);
}

int get_nxt(int now, int k) {
	int l = now, r = n, re;
	while (l <= r) {
		int mid = (l + r) >> 1;
		if (clac(now, mid) <= k) re = mid, l = mid + 1;
			else r = mid - 1;
	}
	return re + 1;
}

struct LCT {
	int sz[N], ls[N], rs[N], fa[N];
	
	bool nrt(int x) {return ls[fa[x]] == x || rs[fa[x]] == x;}
	bool lrs(int x) {return ls[fa[x]] == x;}
	void up(int x) {sz[x] = sz[ls[x]] + sz[rs[x]] + 1;}
	
	void rotate(int x) {
		int y = fa[x], z = fa[y];
		int b = lrs(x) ? rs[x] : ls[x];
		if (z && nrt(y)) (lrs(y) ? ls[z] : rs[z]) = x;
		if (lrs(x)) rs[x] = y, ls[y] = b;
			else ls[x] = y, rs[y] = b;
		fa[x] = z; fa[y] = x;
		if (b) fa[b] = y;
		up(y); up(x);
	}
	
	void Splay(int x) {
		while (nrt(x)) {
			if (nrt(fa[x])) {
				if (lrs(x) == lrs(fa[x])) rotate(fa[x]);
					else rotate(x);
			}
			rotate(x);
		}
	}
	
	void access(int x) {
		int lst = 0;
		for (; x; lst = x, x = fa[x]) {
			Splay(x);
			
			rs[x] = lst;
			up(x);
		}
	}
	
	int find_root(int x) {
		access(x); Splay(x);
		while (ls[x]) x = ls[x];
		Splay(x); 
		return x;
	}
	
	void link(int x, int y) {
		access(x); fa[x] = y;
	}
	
	void cut(int x, int y) {
		access(x); Splay(y);
		fa[x] = 0; rs[y] = 0;
		up(y);
	}
}T;

int main() {
	scanf("%d %d %d", &n, &w, &q);
	for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
	for (int i = 1; i <= q; i++) {
		scanf("%d", &s[i].k); s[i].k = w - s[i].k;
		s[i].id = i;
	}
	sort(s + 1, s + q + 1, cmp);
	for (int i = 1; i <= q; i++) xl[i] = s[i].k;
	
	for (int i = 1; i <= n; i++) maxn[i][0] = minn[i][0] = a[i];
	log2_[0] = -1; for (int i = 1; i <= n; i++) log2_[i] = log2_[i >> 1] + 1;
	for (int i = 1; i <= 17; i++)
		for (int j = 1; j + (1 << i) - 1 <= n; j++) {
			maxn[j][i] = max(maxn[j][i - 1], maxn[j + (1 << (i - 1))][i - 1]);
			minn[j][i] = min(minn[j][i - 1], minn[j + (1 << (i - 1))][i - 1]);
		}
	
	for (int i = 1; i <= n; i++) st[1].push_back(i), nxt[i] = i;
	for (int i = 1; i <= q; i++) {
		for (int j = 0; j < st[i].size(); j++) {
			int now = st[i][j], fr = nxt[now] + 1;
			T.cut(now, nxt[now]);
			while (fr <= n && fr - now <= B && clac(now, fr) <= s[i].k) fr++;
			if (fr - now > B) bl[now] = 1;
				else {
					int to = lower_bound(xl + 1, xl + q + 1, clac(now, fr)) - xl;
					nxt[now] = fr; T.link(now, nxt[now]);
					st[to].push_back(now);
				}
		}
		for (int j = 1; j <= n; j = get_nxt(j, s[i].k)) {
			if (!bl[j]) {
				T.access(j); T.Splay(j); ans[s[i].id] += T.sz[j] - 1;
				j = T.find_root(j);
			}
			if (j == n + 1) break;
			ans[s[i].id]++;
		}
	}
	
	for (int i = 1; i <= q; i++) printf("%d\n", ans[i] - 1);
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值