5.30 杂题选讲

nlogn 点对#

CodeChef - Minimum Xor Segment#

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

ai,aj,ak 在二进制下的 lcp 长为 d,我们比较他们的第 d+1 位,设三者分别为 x,y,z

  • x=y 时,显然 ai xor aj<ai xor ak

  • y=z 时,显然 aj xor ak<ai xor ak

  • x=z 是,有用。

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

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

点击查看代码
#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[0,17],我们把 [2k,2k+1) 分一块。

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

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

如果不小于,那么 sum 加上 val 后一定 2k+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,设我们当前的能力值 2k。那么对于血量 <2k 的怪一定能被打败。

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

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

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

点击查看代码
#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 分为两半:k2k2,分别挂在前缀和后缀上。

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

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

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

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

出处:https://www.cnblogs.com/Sktn0089/p/18223671

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   Lgx_Q  阅读(44)  评论(0编辑  收藏  举报
more_horiz
keyboard_arrow_up light_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示