Loading

【题解】LOJ 6029 -「雅礼集训 2017 Day1」市场

题目大意

题目链接

给定一个长度为 \(n\) 的序列 \(a\) ,元素编号为 \([0, n - 1]\) 。试维护一个支持以下操作的数据结构:

  1. 区间 \([l, r]\) 内的元素都 \(+ c\)

  2. 对于 \(l \leq i \leq r\)\(a_i = \lfloor \frac{a_i}{d} \rfloor\)

  3. 询问区间 \([l, r]\) 的最小值

  4. 询问区间 \([l, r]\) 的元素和

\(n \leq 10^5, -10^4 \leq c \leq 10^4, 2\leq d \leq 10^9\)

解题思路

对区间进行操作,首先考虑线段树和平衡树。观察题目,发现用 线段树 维护给出的操作更加方便。可以注意到操作 \(1, 2, 4\) 都是模板,考虑第 \(3\) 个操作如何实现。

我们考虑增加一个 \(lazy\) 标记。我们给区间整体除以一个数 \(d\),最小值以及元素之和都会缩小到原来的 \(\frac{1}{d}\) 。但是,如果我们尝试叠加两个 \(lazy\) 标记 \(a\)\(b\) ,我们会发现不可以直接将子结点的 \(lazy\) 更新为 \(a \times b\) 。原因是题目中给出的操作还需要下取整,设 \(k = \lfloor \frac{c}{a} \rfloor\) ,可能会出现某个值 \(c\) 满足 \(\lfloor \frac{c}{ab} \neq \lfloor \frac{k}{b} \rfloor\)

因此,经过若干尝试,我们发现无法将一个特殊的 \(lazy\) 标记套到线段树上完成操作。此时有两种选择:

  1. 用其他算法来辅助维护

  2. \(lazy\) 标记转化成容易维护的标记

显然,这道题目并没有什么显著的算法特征。因此,我们考虑综合几种线段树模型的思路,将除法的操作转化成加法的维护。

我们可以效仿区间开方线段树的做法。容易发现,当一个区间经过了若干次操作以后,该区间包含的所有元素都会逐渐靠近于 \(0\),最终变成相同的数。我们可以发现,当区间中元素的最大值等于 \(10^9\) 时,我们只需要 \(30\) 次除法操作就可以令区间中的所有元素都变成 \(\leq 0\) 。如果我们想要破坏这种结构,修改单点的最坏复杂度也只有 \(O(logn)\) ,因此总复杂度不会变得更坏。

当区间中的元素各不相同时,我们只能进行多次单点修改。但是假设 \(l \leq i \leq r\)\(\max(a_i) = p, \min(a_i) = q\) 。假设 \(i\) 为一自然数,因为当 \(i\) 递增时,\(\lfloor \frac{i}{d} \rfloor\) 严格不降。如果 \(\lfloor \frac{p}{d} \rfloor = \lfloor \frac{q}{d} \rfloor\) ,此时取值范围在 \((q, p)\) 之间的所有元素除以 \(d\) 再下取整的结果都是 \(\lfloor \frac{p}{d} \rfloor\) 。因为 \(\forall l \leq i \leq r, \lfloor \frac{a_i}{d} \rfloor\) 都相等,这个式子等价于 \(\forall l \leq i \leq r, a_i - (a_i - \lfloor \frac{a_i}{d} \rfloor)\) 都相等。

所以我们在维护除法操作时可以判断:如果 \(p - \lfloor \frac{p}{d} \rfloor = q - \lfloor \frac{q}{d} \rfloor\) ,说明该区间中所有的 \(a_i - (a_i - \lfloor \frac{a_i}{d} \rfloor)\) ,即 \(\lfloor \frac{a_i}{d} \rfloor\) 都相等。证明:如果存在某个 \(a_i\) 满足 \(a_i - (a_i - \lfloor \frac{a_i}{d} \rfloor)\) 与其他 \(a_i\) 计算的结果不相等,说明 \(\lfloor \frac{a_i}{d} \rfloor\) 与其他值不等,也就是 \(a_i\) 的取值范围不在 \([p, q]\) 内,与 \(p, q\) 的定义不符。所以满足 \(p - \lfloor \frac{p}{d} \rfloor = q - \lfloor \frac{q}{d} \rfloor\) 时,区间内所有的 \(\lfloor \frac{a_i}{d} \rfloor\) 都相等。因为 \(p - \lfloor \frac{p}{d} \rfloor = q - \lfloor \frac{q}{d} \rfloor\) 等价于 \(p - \lfloor \frac{p}{d} \rfloor = q - \lfloor \frac{p}{d} \rfloor\) ,等式两边同时加上 \(\lfloor \frac{p}{d} \rfloor\) ,得到 \(p = q\) 。由 \(p = q\) 可知区间内所有元素相等,所有元素都减去 \(p - \lfloor \frac{p}{d} \rfloor\) 就可以得到 \(\lfloor \frac{a_i}{d} \rfloor\) 的结果。 上文提到的式子都是等价的,我们直接判断其一即可。

所以这道题的除法操作可以转化成加法操作。我们只需要在线段树中维护最大值、最小值和区间和,对于操作 \(1, 2, 4\),我们直接维护即可。对于操作 \(3\),如果被修改的区间所有元素值都相等,我们就直接给整个区间减去最大值除以 \(d\) 再下取整的结果,否则进行暴力单点修改。时间复杂度约为 \(O(nlogn)\) ,时限 \(2\) 秒可以通过。

参考代码

#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
#define int long long

const int maxn = 1e5 + 5;
const int inf = 1e16;

struct node {
	int l, r, sum;
	int maxv, minv, lazy;
} tree[maxn << 2];

int n, q;
int a[maxn];

inline int read() {
	int res = 0, flag = 1;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		if (ch == '-') {
			flag = -1;
		}
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		res = res * 10 + ch - '0';
		ch = getchar();
	}
	return res * flag;
}

void push_up(int k) {
	tree[k].maxv = max(tree[2 * k].maxv, tree[2 * k + 1].maxv);
	tree[k].minv = min(tree[2 * k].minv, tree[2 * k + 1].minv);
	tree[k].sum = tree[2 * k].sum + tree[2 * k + 1].sum;
}

void push_down(int k) {
	if (tree[k].l == tree[k].r) {
		tree[k].lazy = 0;
		return;
	}
	tree[2 * k].lazy += tree[k].lazy;
	tree[2 * k + 1].lazy += tree[k].lazy;
	tree[2 * k].sum += (tree[k].lazy * (tree[2 * k].r - tree[2 * k].l + 1));
	tree[2 * k + 1].sum += (tree[k].lazy * (tree[2 * k + 1].r - tree[2 * k + 1].l + 1));
	tree[2 * k].maxv += tree[k].lazy;
	tree[2 * k + 1].maxv += tree[k].lazy;
	tree[2 * k].minv += tree[k].lazy;
	tree[2 * k + 1].minv += tree[k].lazy;
	tree[k].lazy = 0;
}

void build(int k, int l, int r) {
	tree[k].l = l;
	tree[k].r = r;
	if (l == r) {
		tree[k].sum = tree[k].maxv = tree[k].minv = a[l];
		return;
	}
	int mid = (l + r) / 2;
	build(2 * k, l, mid);
	build(2 * k + 1, mid + 1, r);
	push_up(k);
}

void update_add(int k, int l, int r, int w) {
	if (tree[k].l >= l && tree[k].r <= r) {
		tree[k].sum += (w * (tree[k].r - tree[k].l + 1));
		tree[k].lazy += w;
		tree[k].maxv += w;
		tree[k].minv += w;
		return;
	}
	push_down(k);
	int mid = (tree[k].l + tree[k].r) / 2;
	if (l <= mid) {
		update_add(2 * k, l, r, w);
	}
	if (r > mid) {
		update_add(2 * k + 1, l, r, w);
	}
	push_up(k);
}

void update_div(int k, int l, int r, int w) {
	if (tree[k].l >= l && tree[k].r <= r) {
		int a = tree[k].maxv - floor(tree[k].maxv * 1.0 / w);
		int b = tree[k].minv - floor(tree[k].minv * 1.0 / w);
		if (a == b) {
			int cur = a;
			tree[k].sum -= (cur * (tree[k].r - tree[k].l + 1));
			tree[k].lazy -= cur;
			tree[k].maxv -= cur;
			tree[k].minv -= cur;
			return;
		}
	}
	push_down(k);
	int mid = (tree[k].l + tree[k].r) / 2;
	if (l <= mid) {
		update_div(2 * k, l, r, w);
	}
	if (r > mid) {
		update_div(2 * k + 1, l, r, w);
	}
	push_up(k);
}

int query_min(int k, int l, int r) {
	if (tree[k].l >= l && tree[k].r <= r) {
		return tree[k].minv;
	}
	push_down(k);
	int mid = (tree[k].l + tree[k].r) / 2, res = inf;
	if (l <= mid) {
		res = min(res, query_min(2 * k, l, r));
	}
	if (r > mid) {
		res = min(res, query_min(2 * k + 1, l, r));
	}
	return res;
}

int query_sum(int k, int l, int r) {
	if (tree[k].l >= l && tree[k].r <= r) {
		return tree[k].sum;
	}
	push_down(k);
	int mid = (tree[k].l + tree[k].r) / 2, sum = 0;
	if (l <= mid) {
		sum += query_sum(2 * k, l, r);
	}
	if (r > mid) {
		sum += query_sum(2 * k + 1, l, r); 
	}
	return sum;
}

signed main() {
	int opt, l, r, c, d;
	n = read();
	q = read();
	for (int i = 1; i <= n; i++) {
		a[i] = read();
	}
	build(1, 1, n);
	for (int i = 1; i <= q; i++) {
		opt = read();
		l = read();
		r = read();
		l++, r++;
		if (opt == 1) {
			c = read();
			update_add(1, l, r, c);
		} else if (opt == 2) {
			d = read();
			update_div(1, l, r, d);
		} else if (opt == 3) {
			printf("%lld\n", query_min(1, l, r));
		} else if (opt == 4) {
			printf("%lld\n", query_sum(1, l, r));
		}
	}
	return 0;
}
posted @ 2021-07-24 23:43  kymru  阅读(83)  评论(0编辑  收藏  举报