【luogu P7470】岛屿探险

岛屿探险

题目链接:luogu P7470

题目大意

有一些岛屿,每个有劳累值 ai 和有趣度 bi。
然后多次询问,给出 c,d 和一个区间范围,问你这个区间范围内有多少个岛屿可以满足 a⊕c<=min(b,d) 这个式子。

思路

首先,我们直接弄这道题没有思路,那我们看看部分分怎么搞。

然后你会发现有两个部分分很特殊,就是式子右边一定是 b 小,或者是 d 小。
那你先考虑这样个情况怎么做。

d 永远是小的

那我们其实就已经不用管 \(b\) 了。
那现在麻烦的就是异或了。
异或,我们就会想到用 Trie。

那岛屿有用的值只有 a,你每次 c,d 都有用,那我们考虑用 a 来建 Trie。
那每次要怎么拿 c,d 去匹配呢?

那肯定是从高位逐位匹配,然后根据它在 Trie 树上跑。
如果 d 的这一位是 0,那为了要让 d 大于等于,那 a 和 c 的这一位就一定要相等。
如果 d 的这一位是 1,那就可以选相等或者不相等。如果相等,那后面都不用匹配了已经会大了,那就直接统计这样的个数。然后再继续往不相等的地方跑。

(统计个数我们可以直接给插入时路径上的点就标记,然后就用标记的次数得出)

那多组询问怎么搞呢?
想到区间,而且前面说了是统计标记次数,自然想到前缀和,那就是可持续化 Trie 树。

b 永远是最小的

那我们试着用上面的思路来,那这次 d 就是没用的了。

那我们会发现如果要用上面的方法来搞的话,每次查询就要每个岛有两个信息,就不知道怎么搞。

那你再想想上面是怎么搞的,它是因为你每次要搜岛的量只有一个,而我们这里是每次查询的量只有一个。

那你就会有一个奇妙的想法:啊!我能不能把查询变成小岛,把小岛变成查询,然后做呢?
啊别说,还真是这么做的。

然后我们具体讲讲如何实现。
那首先,很明显第一步是要把查询给离线了吧。
然后想上面的一样,我们把只有一个变化的(也就是 c)来建 Trie。

那接着跟着上面,就把 a,b 丢进去查询。
那这里很明显就不是要查询了,而是要另外的操作。
那这个操作就是把它贡献到这个 c 里面去(因为 c 终究还是询问)。

那我们前面是给树打标记,然后去搜碰到多少个标记。那我们这里就是去搜,搜到的位置打标记,然后再重新用 c 跑,看 c 跑的路径有多少个标记。

然后接着来看多次询问。
那也是弄成前缀和的形式,你就查询 \(1\sim r\)\(1\sim l-1\) 的。
那包括标记,左端点都是一样的,就按右端点排序,然后之前标记一下它是属于要加的还是要减的: \(1\sim r\) 的是加,\(1\sim l-1\) 的是减。(当然也要和标记区分开)

如何合在一起

你会发现单纯的合在一起肯定是不行的。

那你考虑先排序(\(b\) 为第一关键字),然后就搞 \(\text{cdq}\) 分治。
(因为这样你每次只用考虑右半边对左半边的贡献)

具体怎么搞呢?
首先取出右半边所有的 \(a\),左半边的 \(c,d\),然后就是 d 永远最小的情况。同理,取出右半边所有的 \(c\),左半边的 \(a,b\),然后就是 b 永远最小的情况。

然后就可以得出答案了。

代码

#include<cstdio>
#include<algorithm>

using namespace std;

struct node {
	int a, pl;
}e[200001], ee[300001];
struct Trie {
	int son[2], sum;
}trie[6000001];
int n, q, KK, ans[100001];
int a[100001], b[100001], c[100001], d[100001], ql[100001], qr[100001];
int val[100001], Trie_num, root[100001];

bool cmp(node x, node y) {
	if (x.a == y.a) return x.pl < y.pl;
	return x.a < y.a;
}

int get_new(int now) {
	trie[++Trie_num] = trie[now];
	return Trie_num;
}

int insert1(int now, int x) {//可持续化的 Trie 树
	int root_ = get_new(now);
	
	int now_ = root_;
	trie[now_].sum++;
	for (int i = 23; i >= 0; i--) {
		int go = (x >> i) & 1;
		trie[now_].son[go] = get_new(trie[now].son[go]);
		trie[trie[now_].son[go]].sum++;
		now_ = trie[now_].son[go];
		now = trie[now].son[go];
	}
	
	return root_;
}

int query1(int x, int y, int c, int d) {//两个时刻的Trie树搜出的值相减
	int re = 0;
	
	for (int i = 23; i >= 0; i--) {
		if (!x) break;
		
		int cgo = (c >> i) & 1;
		int dgo = (d >> i) & 1;
		if (dgo) {
			re += trie[trie[x].son[cgo]].sum - trie[trie[y].son[cgo]].sum;
		}
		
		x = trie[x].son[cgo ^ dgo];
		y = trie[y].son[cgo ^ dgo];
	}
	
	re += trie[x].sum - trie[y].sum;
	
	return re;
}

void insert2(int now, int x) {//普通 Trie 树
	for (int i = 23; i >= 0; i--) {
		int go = (x >> i) & 1;
		if (!trie[now].son[go]) trie[now].son[go] = get_new(0);
		now = trie[now].son[go];
	}
}

void add2(int now, int c, int d) {//打标记
	for (int i = 23; i >= 0; i--) {
		if (!now) break;
		
		int cgo = (c >> i) & 1;
		int dgo = (d >> i) & 1;
		if (dgo && trie[now].son[cgo]) trie[trie[now].son[cgo]].sum++;
		
		now = trie[now].son[cgo ^ dgo];
	}
	
	if (now) trie[now].sum++;
}

int query2(int now, int x) {//查询遇到多少标记
	int re = 0;
	
	for (int i = 23; i >= 0; i--) {
		re += trie[now].sum;
		int go = (x >> i) & 1;
		now = trie[now].son[go];
	}
	
	re += trie[now].sum;
	
	return re;
}

void cdq(int l, int r) {//cdq 分治
	if (l == r) return ;
	
	int mid = (l + r) >> 1;
	cdq(l, mid);
	cdq(mid + 1, r);
	
	//b[i] > d[i]
	int num = 0;
	for (int i = mid + 1; i <= r; i++)
		if (e[i].pl <= n) {
			val[++num] = e[i].pl;
		}
	sort(val + 1, val + num + 1);
	
	Trie_num = 0;
	for (int i = 1; i <= num; i++) {
		root[i] = insert1(root[i - 1], a[val[i]]);
	}
	
	for (int i = l; i <= mid; i++)
		if (e[i].pl > n) {
			int id = e[i].pl - n;
			int L = lower_bound(val + 1, val + num + 1, ql[id]) - val;
			int R = upper_bound(val + 1, val + num + 1, qr[id]) - val - 1;
			if (L > R) continue;
			
			ans[id] += query1(root[R], root[L - 1], c[id], d[id]);
		}
	
	//b[i] <= d[i]
	Trie_num = 0;
	get_new(0);
	num = 0;
	for (int i = mid + 1; i <= r; i++)
		if (e[i].pl > n) {
			int id = e[i].pl - n;
			insert2(1, c[id]);
			
			ee[++num] = (node){ql[id] - 1, id + n};
			ee[++num] = (node){qr[id], id + n + q};
		}
	
	for (int i = l; i <= mid; i++)
		if (e[i].pl <= n) {
			ee[++num] = (node){e[i].pl, e[i].pl};
		}
	
	sort(ee + 1, ee + num + 1, cmp);
	
	for (int i = 1; i <= num; i++) {
		if (ee[i].pl <= n) {
			add2(1, a[ee[i].pl], b[ee[i].pl]);
		}
		else if (ee[i].pl <= n + q) {
			int id = ee[i].pl - n;
			ans[id] -= query2(1, c[id]);
		}
		else {
			int id = ee[i].pl - n - q;
			ans[id] += query2(1, c[id]);
		}
	}
}

int main() {
	scanf("%d %d", &n, &q);
	for (int i = 1; i <= n; i++) {
		scanf("%d %d", &a[i], &b[i]);
		e[++KK] = (node){b[i], i};
	}
	for (int i = 1; i <= q; i++) {
		scanf("%d %d %d %d", &ql[i], &qr[i], &c[i], &d[i]);
		e[++KK] = (node){d[i], n + i};
	}
	
	sort(e + 1, e + n + q + 1, cmp);
	cdq(1, n + q);
	
	for (int i = 1; i <= q; i++)
		printf("%d\n", ans[i]);
	
	return 0;
}
posted @ 2021-04-04 22:51  あおいSakura  阅读(91)  评论(0编辑  收藏  举报