Loading

5.30 杂题选讲

\(n\log n\) 点对

CodeChef - Minimum Xor Segment

考虑对于三个数 \(1\le i < j < k\le n\),我们观察 \((i,k)\) 什么时候是有用的。

\(a_i,a_j,a_k\) 在二进制下的 \(\text{lcp}\) 长为 \(d\),我们比较他们的第 \(d+1\) 位,设三者分别为 \(x,y,z\)

  • \(x = y\) 时,显然 \(a_i\text{ xor } a_j < a_i \text{ xor } a_k\)

  • \(y = z\) 时,显然 \(a_j\text{ xor } a_k < a_i \text{ xor } a_k\)

  • \(x = z\) 是,有用。

我们先随便找一个 \(i\),然后不断往前找到第一个有用的 \(i\)

每往前找一个 \(i\),容易发现 \(\text{lcp}\) 长度都会 \(+1\),于是最多找 \(30\) 个,总点对数为 \(O(n\log n)\)

点击查看代码
#include <bits/stdc++.h>
#define ll int
#define fi first
#define se second
#define mkp make_pair//
#define pir pair <ll, ll>
#define pb push_back
using namespace std;
const ll maxn = 2e5 + 10;
ll trie[maxn * 30][2], pos[maxn * 30], tot = 1, n, q;
ll L[maxn], R[maxn], a[maxn];
vector <ll> vec[maxn];
ll tree[maxn];
void add(ll x, ll v) {
	while(x <= n) {
		tree[x] = min(tree[x], v);
		x += x & -x;
	}
}
ll ask(ll x) {
	ll v = 2e9;
	while(x) {
		v = min(v, tree[x]);
		x ^= x & -x;
	}
	return v;
}
ll ans[maxn];
int main() {
	scanf("%d%d", &n, &q);
	memset(tree, 0x3f, sizeof tree);
	for(ll i = 1; i <= n; i++)
		scanf("%d", a + i);
	for(ll i = 1; i <= q; i++) {
		scanf("%d%d", L + i, R + i);
		vec[R[i]].pb(i);
	}
	for(ll i = 1; i <= n; i++) {
		if(i) add(n - i + 2, a[i] ^ a[i - 1]);
		ll p = 1;
		for(ll j = 29; ~j; j--) {
			ll c = (a[i] >> j) & 1;
			if(trie[p][c]) {
				p = trie[p][c];
				add(n - pos[p] + 1, a[i] ^ a[pos[p]]);
			}
			else break;
		}
		for(ll j: vec[i])
			ans[j] = ask(n - L[j] + 1);
		p = 1;
		for(ll j = 29; ~j; j--) {
			ll c = (a[i] >> j) & 1;
			if(!trie[p][c]) trie[p][c] = ++tot;
			p = trie[p][c];
			pos[p] = i;
		}
	}
	for(ll i = 1; i <= q; i++)
		printf("%d\n", ans[i]);
	return 0;
}

倍增分块

2019ICPC徐州 H - Yuuki and a problem

考虑值域上倍增分块。具体的,对于 \(k\in [0,17]\),我们把 \([2^k, 2^{k + 1})\) 分一块。

根据神秘数那题的做法,我们从小到大加入数。

对于一个 \(k\),设当前 \(sum\ge 2^k\),我们找出这个块里面的最小值 \(val\),判断 \(sum+1\) 是否小于 \(k\)

如果不小于,那么 \(sum\) 加上 \(val\) 后一定 \(\ge 2^{k + 1}\),可以直接跳到下一个块。

点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define fi first
#define se second
#define mkp make_pair
#define pir pair <ll, ll>
#define pb push_back
using namespace std;
const ll maxn = 2e5 + 10, inf = 1e18;
ll n, q, a[maxn];
struct SGT {
	ll mn[maxn << 2], sum[maxn << 2];
	void modify(ll p, ll l, ll r, ll x, ll v) {
		if(l == r) {
			if(v == -1) mn[p] = inf, sum[p] = 0;
			else mn[p] = sum[p] = v;
			return;
		}
		ll mid = l + r >> 1;
		if(x <= mid) modify(p << 1, l, mid, x, v);
		else modify(p << 1|1, mid + 1, r, x, v);
		mn[p] = min(mn[p << 1], mn[p << 1|1]);
		sum[p] = sum[p << 1] + sum[p << 1|1];
	}
	ll querymn(ll p, ll l, ll r, ll ql, ll qr) {
		if(ql <= l && r <= qr) return mn[p];
		if(r < ql || qr < l) return inf;
		ll mid = l + r >> 1;
		return min(querymn(p << 1, l, mid, ql, qr), 
		querymn(p << 1|1, mid + 1, r, ql, qr));
	}
	ll querysum(ll p, ll l, ll r, ll ql, ll qr) {
		if(ql <= l && r <= qr) return sum[p];
		if(r < ql || qr < l) return 0;
		ll mid = l + r >> 1;
		return querysum(p << 1, l, mid, ql, qr) +
		querysum(p << 1|1, mid + 1, r, ql, qr);
	}
	void build() {
		for(ll i = 1; i <= 4 * n; i++)
			mn[i] = inf, sum[i] = 0;
	}
} tr[18];
ll Log[maxn];
int main() {
	scanf("%lld%lld", &n, &q);
	for(ll i = 0; i <= 17; i++) tr[i].build();
	for(ll i = 2; i <= 2e5; i++) Log[i] = Log[i >> 1] + 1;
	for(ll i = 1; i <= n; i++) {
		scanf("%lld", a + i);
		tr[Log[a[i]]].modify(1, 1, n, i, a[i]);
	}
	while(q--) {
		ll op, x, y;
		scanf("%lld%lld%lld", &op, &x, &y);
		if(op == 1) {
			tr[Log[a[x]]].modify(1, 1, n, x, -1);
			tr[Log[y]].modify(1, 1, n, x, a[x] = y);
		} else {
			ll sum = 0;
			for(ll i = 0; i <= 17; i++) {
				ll tmp = tr[i].querymn(1, 1, n, x, y);
				if(sum + 1 < tmp) break;
				sum += tr[i].querysum(1, 1, n, x, y);
			}
			printf("%lld\n", sum + 1);
		}
	}
	return 0;
}

[IOI2021] 地牢游戏

倍增分块,对于每个 \(k\),设我们当前的能力值 \(\ge 2^k\)。那么对于血量 \(< 2^k\) 的怪一定能被打败。

对于血量 \(\ge 2^k\) 的怪,我们不确定能不能打败。但是一旦打败了,能力值一定 \(\ge 2^{k + 1}\),即一定会跳到后面的块。

考虑倍增,我们在块 \(k\) 内走的时候,先默认 \(\ge 2^k\) 的怪打不过,然后倍增跳到第一个能打败的怪。

对于每个 \(k\),预处理出我们走 \(2^i\) 后到达的怪,获得的能力值,以及如果要被所有能力值 \(\ge 2^k\) 的怪打败,那么能力值最多为多少。

点击查看代码
#include <bits/stdc++.h>
#define ll int
#define fi first
#define se second
#define mkp make_pair
#define pir pair <ll, ll>
#define pb push_back
using namespace std;
const ll maxn = 4e5 + 10, M = 5, base = 32, K = 23;
const long long inf = 1e18;
struct Bz {
	ll to[maxn][25];
	long long sum[maxn][25], lim[maxn][25]; 
} D[6];
ll pw[6], N, S[maxn], W[maxn];
void init(ll n, vector <ll> s, vector <ll> p, vector <ll> w, vector <ll> l) {
	pw[0] = 1, N = n;
	for(ll i = 1; i <= M; i++) pw[i] = pw[i - 1] * base;
	for(ll i = 0; i < N; i++)
		S[i] = s[i], W[i] = w[i];
	for(ll u = 0; u <= M; u++) {
		for(ll i = 0; i < N; i++) {
			if(pw[u] > s[i]) {
				D[u].to[i][0] = w[i];
				D[u].lim[i][0] = inf;
				D[u].sum[i][0] = s[i];
			} else {
				D[u].to[i][0] = l[i];
				D[u].lim[i][0] = s[i];
				D[u].sum[i][0] = p[i];
			}
		}
		D[u].to[N][0] = -1;
		for(ll j = 1; j <= K; j++) {
			for(ll i = 0; i <= N; i++) {
				ll p = D[u].to[i][j - 1];
				if(p == -1 || D[u].to[p][j - 1] == -1) {
					D[u].to[i][j] = -1;
					continue;
				}
				D[u].to[i][j] = D[u].to[p][j - 1];
				D[u].lim[i][j] = min(D[u].lim[i][j - 1], D[u].lim[p][j - 1] - D[u].sum[i][j - 1]);
				D[u].sum[i][j] = D[u].sum[i][j - 1] + D[u].sum[p][j - 1];
			}
		}
	}
}
long long simulate(ll x, ll _z) {
	long long z = _z;
	for(ll u = 0; u <= M;) {
		while(u < M && z >= pw[u + 1]) ++u;
		for(ll i = K; ~i; i--)
			if(D[u].to[x][i] != -1 && D[u].lim[x][i] > z) {
				z += D[u].sum[x][i];
				x = D[u].to[x][i];
			}
		if(x == N) return z;
		z += S[x], x = W[x];
	}
}

减半报警器

CF gym104065B - Call Me Call Me

区间很难维护。

考虑猫树分治,每个区间转化成了一段前缀 + 一段后缀。

我们把一个区间的 \(k\) 分为两半:\(\lfloor \dfrac k2 \rfloor\)\(\lceil \dfrac k2 \rceil\),分别挂在前缀和后缀上。

当第 \(i\) 个人加入时,我们在猫树上的每一层,开一个线段树来维护对应的前缀 / 后缀,每次做区间 \(-1\) 操作。

当一个位置上挂的数 \(<0\) 时,我们重新分配对应的 \(k\) 于两半。

这样一个区间的 \(k\) 被重新分配的次数为 \(O(\log n)\),代码还没写。

包括鬼界那题也是这个思想

posted @ 2024-05-31 07:27  Lgx_Q  阅读(30)  评论(0编辑  收藏  举报