首页 > 其他分享 >「突刺贯穿第二分块」P4117 [Ynoi2018] 五彩斑斓的世界

「突刺贯穿第二分块」P4117 [Ynoi2018] 五彩斑斓的世界

时间:2023-09-03 16:00:15浏览次数:43  
标签:P4117 Ynoi2018 分块 int tag 即可 平移 突刺

很帅气!

分块在线转离线,考虑每个块对于询问的贡献。
维护块的 max 和 tag 分别代表最大值和减了多少。

先考虑整块,
\(max < 2 * x:\) 每次暴力区间平移即可。
否则对于 \([1, x]\) 全部加上 \(x\) 平移到 \([x + 1, x * 2]\),然后区间整体减 \(x\) 即可。
散块怎么做?暴力减,然后重构块就可以了。
那么我们怎么 听起来很简单是不是?可是怎么维护区间平移?我们发现在大量打 tag 的情况下,暴力不太好做。使用并查集即可。

复杂度显然直接用势能线段树那一套分析即可,注意我们要开两倍值域空间。

#include <bits/stdc++.h>
#define rep(i, l, r) for (int i = l; i <= r; i ++)
#define per(i, r, l) for (int i = r; i >= l; i --)

/*\yhx12243/ 鱼大保佑*/
/*「突刺贯穿第二分块」*/
using namespace std;

//#define getchar() (p1 == p2&&(p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)
//char buf[1 << 21], *p1 = buf, *p2 = buf;

inline int qread() {
	register char c = getchar();
	register int x = 0, f = 1;
	while (c < '0' || c > '9') {
		if (c == '-') f = -1;
		c = getchar();
	}
	while (c >= '0' && c <= '9') {
		x = (x << 3) + (x << 1) + c - 48;
		c = getchar();
	}
	return x * f;
}

const int ___ = 1e6 + 5, __ = 5e5 + 5, _ = 1e5 + 5;
int n, m, bsz;
int a[___];

struct qry {
	int op, l, r, x, ans;
} q[__];

struct second_block {
	int l, r, mx, tag;
	int val[___], sz[_ + 1000], anc[___], rt[_];
	int find (int x) { return x == anc[x] ? x : anc[x] = find(anc[x]); }
	void build () {
		mx = tag = 0;
		rep(i, l, r) {
			mx = max(mx, a[i]);
			if (! rt[a[i]])
				anc[i] = rt[a[i]] = i, val[i] = a[i];
			else 
				anc[i] = rt[a[i]];
			sz[a[i]] ++;
		}
	}
	void merge (int x, int y) {
		if (rt[y]) anc[rt[x]] = rt[y];
		else {
			rt[y] = rt[x],
			val[rt[y]] = y;
		}
		sz[y] += sz[x], sz[x] = rt[x] = 0;
	} // x -> y
	void assign (int x) {
		if (mx - tag > (x << 1)) {
			rep(i, tag + 1, tag + x)
				if (rt[i]) merge(i, i + x);
			tag += x;
		} else {
			per(i, mx, tag + x + 1)
				if (rt[i]) merge(i, i - x);
			mx = min(mx, tag + x);
		}
	}
	void remake () {
		rep(i, l, r) {
			a[i] = val[find(i)], rt[a[i]] = sz[a[i]] = 0;
			a[i] -= tag;
		}
		rep(i, l, r) anc[i] = val[i] = 0;
	}
	void brute (int s, int t, int x) {
		int p = max(s, l), q = min(t, r);
		remake();
		rep(i, p, q) a[i] -= x * (a[i] > x);
		build();
	}
	void query (int x) {
		if (q[x].l <= l && r <= q[x].r) 
			q[x].ans += sz[q[x].x + tag];
		else {
			int s = max(q[x].l, l), t = min(q[x].r, r);
			rep(i, s, t) if (val[find(i)] - tag == q[x].x) q[x].ans ++;
		}
	}
} blk;
int main () {
	n = qread(), m = qread();
	rep(i, 1, n) a[i] = qread();
	rep(i, 1, m) {
		q[i].op = qread(), q[i].l = qread(), q[i].r = qread(), q[i].x = qread();
	}
	bsz = sqrt(n);
	for (int ptr = 1; ptr <= n; ptr += bsz) {
		blk.l = ptr, blk.r = ptr + bsz - 1;
		blk.build();
		rep(i, 1, m) {
			if (blk.r < q[i].l || q[i].r < blk.l) continue ;
			if (q[i].op == 1) {
				if (q[i].l <= blk.l && blk.r <= q[i].r)
					blk.assign(q[i].x);
				else blk.brute(q[i].l, q[i].r, q[i].x);
			} else if (blk.tag + q[i].x <= __) blk.query(i);
		}
		blk.remake();
	}
	rep(i, 1, m) if (q[i].op == 2) printf("%d\n", q[i].ans);
	return 0;
}

标签:P4117,Ynoi2018,分块,int,tag,即可,平移,突刺
From: https://www.cnblogs.com/Custlo/p/17675081.html

相关文章

  • CF896E/洛谷 P4117 [Ynoi2018]五彩斑斓的世界/Welcome home, Chtholly
    分块。我们先来考虑修改对整块的影响。记值域为\(V=10^5\)。考虑对每一块维护\(V\)个集合\(S_1,S_2,\cdots,S_V\),第\(i\)个集合\(S_i\)维护了区间中所有\(=i\)的元素的一些信息,并维护区间的最大值\(m\),对于一次操作\(x\):若\(m\le2x\),我们暴力对每个\(i\in[x+1,......
  • [Ynoi2018] 天降之物
    [Ynoi2018]天降之物这个根号分治太神啦。首先考虑一个朴素的暴力:对每个数维护出现位置的std::vector这样查询可以两个指针遍历std::vector做到平方复杂度。注意到复杂度和出现次数有关,那么就可以考虑阈值分治了,然而合并的操作使得我们不好维护信息。先考虑不带修的情况,对......
  • 突刺贯穿的第二分块
    [CF896E]Welcomehome,Chtholly给定一个长为\(n\)的序列\(a_1\sima_n\),\(m\)次操作,分两种:1lrx,将\(a_l\sima_r\)中\(\gtx\)的数减去\(x\)。2lr......
  • 【题解】P5397 [Ynoi2018] 天降之物
    码力人的甜品,口嗨者的末路。感觉手牵手那个题才是第四分块正体,这个不如叫最初根号分治。思路根号分治。对于每个值,把它们分成出现大于根号次和小于等于根号次两类。先......
  • [Ynoi2018]天降之物
    題目描述:你需要实现\(m\)个操作,操作有两种:\(1\).把序列中所有值为\(x\)的数的值变成\(y\)。\(2\).找出一个位置\(i\)满足$a_{i}=x\(,找出一个位置\)j$满足\(a_{j}=y\),使......