zoukankan      html  css  js  c++  java
  • 【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 跑的路径有多少个标记。

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

    如何合在一起

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

    那你考虑先排序((b) 为第一关键字),然后就搞 ( ext{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
    ", ans[i]);
    	
    	return 0;
    }
    
  • 相关阅读:
    博客园设置简约主题
    day25-48
    day1-10
    drf app
    flask 框架 练习
    vue入门 显示数据 操作属性 操作样式 条件渲染
    flask 配置文件 路由 视图 模板 中间件
    flask 使用数据库连接池
    Android 工程中添加依赖
    apk反编译
  • 原文地址:https://www.cnblogs.com/Sakura-TJH/p/luogu_P7470.html
Copyright © 2011-2022 走看看