「2016山东省队集训」 线段树学习之标记永久化

早就接触过这个名词:标记永久化,然而当时并不知道是怎么一回事。今天集训中讲到了,来写篇文章记录一下。

先来看一道例题:

给出一个序列长为N(N<=2*10^5)的序列A,A[i]<=2^30,给定一个常数M,每次询问L,R,X,求所有的以i(L<=i<=R)为起始点,长为M的序列中含有A[i]<X的个数最少是多少,强制在线。(80pts:256MB,100pts:70MB)

离线做法是,每个数都有一个影响的区间,即i-m+1…i。我们对X排序,X变大的时候,在线段树中增加这段区间的值。每次询问就是区间求最小值。线段树NlogN可以完成。

然而这题强制在线。

于是想到主席树。

root[i]表示序列中有i个小于X时的序列的样子。

询问的时候先处理出来有多少个小于X,在主席树上直接找即可。

然而这样做空间会吃不消。因为我们区间修改下传标记的时候会多出来许多节点。

于是标记永久化就发挥作用了。

标记永久化的思想是:对于一个打好的标记,我们不去下传,而是在询问的时候考虑上标记。正确性是因为我们询问是对线段树自顶向下询问,因此到达某个区间l…r的时候之前覆盖过这个区间的大区间的答案我们已经计算完毕了。

于是我们就可以上主席树了。

然而只能拿到80分。

这题的正解是对主席树进行一些空间上的压缩(常数相当大。。。),原理是把叶子节点都缩掉,叶子节点答案相同的都缩为一个点。然后节点的答案和标记我们可以通过相互转化求出,缩掉标记。然后最小值的个数不超过20W,通过第一步剪枝,这棵树只有800W个节点,每个节点上还有左右孩子信息。于是我们可以用一个unsigned long long把左右孩子和标记压缩起来。

于是我们用一个800W的8 Byte数组存储主席树信息就可以过了。。。

常数巨大!!!

80分做法:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
#include <cstdio>
#include <algorithm>
#include <map>
using namespace std;
typedef long long LL;
const int INF = 1009999999;
const int maxn = 200005;
int getint() {
	int r = 0, k = 1; char c = getchar();
	for (; '0' > c || c > '9'; c = getchar()) if (c == '-') k = -1;
	for (; '0' <= c && c <= '9'; c = getchar()) r = r * 10 - '0' + c;
	return r * k;
}
struct list_type {
	int to, next;
} list[maxn << 1];
int cnte = 1, h[maxn];
void ins(int x, int y) {
	list[++cnte].to = y;
	list[cnte].next = h[x];
	h[x] = cnte;
}
namespace CM_tree {
	int tot = 0;
	const int maxnode = maxn * 70;
	int lc[maxnode], rc[maxnode], mi[maxnode], tag[maxnode];
	void change(int &x, int y, int l, int r, int ll, int rr) {
		x = ++tot;
		lc[x] = lc[y]; rc[x] = rc[y]; mi[x] = mi[y]; tag[x] = tag[y];
		if (ll <= l && r <= rr) {
			tag[x] ++; mi[x] ++;
			return;
		}
		int mid = (l + r) >> 1;
		if (ll <= mid) change(lc[x], lc[x], l, mid, ll, rr);
		if (rr > mid) change(rc[x], rc[x], mid+1, r, ll, rr);
		mi[x] = min(mi[lc[x]], mi[rc[x]]) + tag[x];
	}
	int query(int x, int l, int r, int ll, int rr, int Tag) {
		if (ll <= l && r <= rr) return mi[x] + Tag;
		int mid = (l + r) >> 1, ret = INF; Tag += tag[x];
		if (ll <= mid) ret = min(ret, query(lc[x], l, mid, ll, rr, Tag));
		if (rr > mid) ret = min(ret, query(rc[x], mid+1, r, ll, rr, Tag));
		return ret;
	}
}
using CM_tree::change;
using CM_tree::query;
int n, m;
int a[maxn], cnt[maxn], val[maxn], root[maxn], tort[maxn];
int tot;
map<int, int> M;
int CountLess (int x) {
	int l = 0, r = tot;
	while (l < r) {
		int mid = (l + r + 1) >> 1;
		if (x <= val[mid]) r = mid - 1;
		else l = mid;
	}
	return cnt[l];
}
int main() {
	freopen("music.in", "r", stdin);
	freopen("music.out", "w", stdout);
	n = getint(); m = getint();
	for (int i = 1; i <= n; ++i) {
		a[i] = getint();
		M[a[i]]++;
	}
	for (map<int, int>::iterator it = M.begin(); it != M.end(); ++it) {
		++tot;
		cnt[tot] = it->second + cnt[tot-1];
		it->second = tot;
		val[tot] = it->first;
		tort[cnt[tot]] = tot;
	}
	for (int i = 1; i <= n; ++i)
		ins(M[a[i]], i);
	for (int i = 1; i <= tot; ++i) {
		int j = h[i];
		if (j) {
			change(root[i], root[i - 1], 1, n, list[j].to - m + 1, list[j].to);
			for (j = list[j].next; j; j = list[j].next) {
				change(root[i], root[i], 1, n, list[j].to - m + 1, list[j].to);
			}
		}
	}
	int lstans = 0, Q = getint(), tmp;
	int L, R, X;
	while (Q--) {
		L = getint(); R = getint(); X = getint();
		X ^= lstans;
		tmp = CountLess(X);
		X = query(root[tort[tmp]], 1, n, L, R, 0);
		printf("%d\n", X);
		lstans = X;
	}
	fclose(stdin);
	fclose(stdout);
	return 0;
}

龙爷50行不到就轻松80,速度还比蒟蒻的快!Orz!!!