2021杭电多校中超联赛第一二场莫队Trie树题汇总题解

Posted 灀龗䯦縷

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2021杭电多校中超联赛第一二场莫队Trie树题汇总题解相关的知识,希望对你有一定的参考价值。

2021杭电多校中超联赛第一二场莫队Trie树题汇总题解


第一场1006 Xor sum

传送门:Xor sum
题意:

  • t (1 ≤ t ≤ 100)组询问,每组询问两行,第一行n (1 ≤ n ≤ 105) 和 k (0 ≤ k ≤ 230),第二行 n 个数字 ai ( 0 ≤ ai ≤ 230)。要求求最短的连续序列,使得aL ^ aL+1 ^ aL+2 ^ … aR-1 ^ aR ≥ k ,每组询问输出满足序列的 L 和 R ,若满足序列有多个则输出 L 最小的。

思路:

  • 首先令数组wi为前i个a数组异或的结果,即前缀异或。由异或符号的性质a^a=0可得
    wL ^ wR = ( a1 ^ a2 ^ … ^ aL ) ^ ( a1 ^ a2 ^ … ^ aR) = aL+1 ^ aL+2 ^ … ^ aR
  • 因此我们需要一种数据结构使得丢进 wR 能返回 w1 ~ wR-1 中与 wR 异或后大于等于 k 的最大 wi ,这样才能使得序列长度最短( 该 wi 即为 wL ) ,因此我们想到建立01Trie树来进行维护。
  • 依次遍历每个前缀异或值,先对 wi 进行询问操作,此时Trie树中为 w1 ~ wi-1 的值,查询以 i 为 R 是否存在满足要求的L,若有则更新答案。
  • 具体询问的方式为使用位运算从高位开始遍历每一位,如果k的当前位为 1 ,那么我们异或的结果只能为 1 才能保证不小于 k ;如果 k 的当前位为 0 ,则异或结果为 0 或 1 都可,当异或结果为 1 时必定满足大于等于k,则用当前节点的 id 数组来更新当前 L ,并向 0 的方向继续寻找是否存在更大的 L 值。如果节点不存在,则表明 w1 ~ wi-1 中不再存在使得大于等于k成立的节点,则退出。
  • 询问操作结束后将 wi 进行插入操作,对于 wi 每一个会经过的节点维护一个 id 数组来记录到达该节点的最大 i 为多少。其实这样操作之后会发现题目要求的 “ 如果多个满足输出 L 最小的 ” 是不需要管的,因为我们每次是固定右端点寻找左端点,如果区间长度与之前寻找的区间长度一样的话左端点必然是大于已经寻找出的最优解的。
  • 时间复杂度 遍历序列O(n),建树O(logn),总计O( nlogn )。具体操作看代码注释。

AC代码(赛后提交405ms)

#include <bits/stdc++.h>
#define x first
#define y second
#define ls u<<1
#define rs u<<1|1
#define debug(x) cout<<"debug"<<' '<<x<<endl;
using namespace std;
const int N = 100010;
const int P = 13331;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int>PII;

int k, t, n, m;
int tr[N * 32][2], idx;
int id[N * 32], w[N];
int l, r, flag;

void insert(int pos, int x) {
	int p = 0;
	for (int i = 29; i >= 0; i--) {
		int t = x >> i & 1;
		if (!tr[p][t]) {
			tr[p][t] = ++idx;
			tr[idx][0] = tr[idx][1] = 0;//用memset会多清空导致tle,但是这样不会
			id[idx] = -1;
		}

		p = tr[p][t];
		id[p] = max(id[p], pos); //记录到达当前节点的最大下标
	}
}

void query(int pos, int x) {
	int p = 0;
	int ans = -1;
	for (int i = 29; i >= 0; i--) {
		int t = (x >> i ) & 1;
		if ((k >> i) & 1) { //k当前位为1,只能朝异或结果为1的方向走
			p = tr[p][1 ^ t];
		} else { //k当前位为0,异或结果0或1都可
			if (tr[p][1 ^ t]) //如果异或结果为1存在
				ans = max(ans, id[tr[p][1 ^ t]]); //更新答案
			p = tr[p][t]; //朝异或结果为0的方向继续寻找是否存在更大值
		}
		if (!p)
			break;//使得>=k的节点不存在则break
	}
	if (p)
		ans = max(ans, id[p]);
	if (ans >= 0 && pos - ans < r - l) {
		flag = 1;
		l = ans, r = pos;
	}

}


int main() {
	cin >> t;
	while (t--) {
		scanf("%d%d", &n, &k);
		//初始化
		l = -1;
		r = n + 1;
		flag = 0;//确定是否存在合法序列满足要求
		tr[0][0] = tr[0][1] = 0;
		idx = 0;

		for (int i = 1; i <= n; i++) {
			int a;
			scanf("%d", &a);
			w[i] = w[i - 1] ^ a;
			query(i, w[i]);
			insert(i, w[i]);
		}
		if (flag) {
			printf("%d %d\\n", l + 1, r);
		} else
			puts("-1");

	}
}

第一场1010 zoto

传送门:zoto
题意:

  • t (1 ≤ t ≤ 5) 组数据,每组数据2 + m 行,第一行n (1 ≤ n ≤ 105) 和 m (0 ≤ m ≤ 105),第二行n个数字ai ( 0 ≤ ai ≤ 105) ,代表 f [ i ] = ai。之后m行询问每行 x0 y0, x1 ,y1 ( 1 ≤ x0 ≤ x1 ≤ n, 0 ≤ y0 ≤ y1 ≤ 105 ),问 区间 [ x0 , x1 ] 中在 [ y0 , y1 ]之间的函数值有多少

思路:

  • dalao们的经典二维数点题,蒟蒻补前置知识都补了好久orz。
  • x轴将所有询问存下用莫队进行离线操作,y轴使用分块来进行计数。学了分块和基础莫队之后再看就都是板子。
  • 时间复杂度 O(n * sqrt(n)+m * sqrt(n) )具体操作看代码注释。

AC代码(赛后提交1669ms)

#include <bits/stdc++.h>
#define x first
#define y second
#define ls u<<1
#define rs u<<1|1
#define debug(x) cout<<"debug"<<' '<<x<<endl;
using namespace std;
const int N = 1000010;
const int P = 13331;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int>PII;

int k, t, n, m;
int w[N], ans[N], cnt[N], tot[N];
int len, leny;

struct query {
	int l, r, a, b, id;
} q[N];

int get(int x) { //返回x轴分块
	return x / len;
}

int gety(int x) { //返回y轴分块
	return x / leny;
}

bool cmp(query x, query y) {
	if (get(x.l) == get(y.l)) {
		if (get(x.l) & 1)
			return x.r < y.r;
		else
			return x.r > y.r;
	}
	return get(x.l) < get(y.l);
}

void add(int x) {
	if (!cnt[x])
		tot[gety(x)]++;//tot记录这个块中拥有的不同数值种类数量
	cnt[x]++;//cnt记录该数值出现总次数
}

void del(int x) {
	cnt[x]--;
	if (!cnt[x])
		tot[gety(x)]--;
}

void get_ans(int id, int l, int r) {
	//分块板子
	int res = 0;
	if (gety(l) == gety(r)) {
		for (int i = l; i <= r; i++)
			if (cnt[i])
				res++;
	} else {
		int i = l, j = r;
		while (gety(i) == gety(l)) {
			if (cnt[i])
				res++;
			i++;
		}
		while (gety(j) == gety(r)) {
			if (cnt[j])
				res++;
			j--;
		}
		for (int k = gety(i); k <= gety(j); k++)
			res += tot[k];
	}
	ans[id] = res;
}

int main() {
	cin >> t;
	while (t--) {
		cin >> n >> m;
		int maxn = 0; //记录y轴最大值用来y轴分块
		memset(cnt, 0, sizeof cnt);
		memset(tot, 0, sizeof tot);
		for (int i = 1; i <= n; i++) {
			scanf("%d", &w[i]);
			maxn = max(maxn, w[i]);
		}

		len = 1.0 * n / sqrt(m) + 1; //x轴一个块的长度
		leny = sqrt(maxn); //y轴一个块的长度

		for (int i = 1; i <= m; i++) {
			int l, r, a, b;
			scanf("%d%d%d%d", &l, &a, &r, &b);
			q[i] = {l, r, a, b, i};
		}

		sort(q + 1, q + 1 + m, cmp);
		//基础莫队板子
		for (int k = 1, j = 1, i = 0; k <= m; k++) {
			int id = q[k].id, l = q[k].l, r = q[k].r;
			int a = q[k].a, b = q[k].b;
			while (i < r)
				add(w[++i]);
			while (i > r)
				del(w[i--]);
			while (j > l)
				add(w[--j]);
			while (j < l)
				del(w[j++]);
			//x轴到达[l ,r]区间后对y轴[a ,b]区间用分块进行查询操作
			get_ans(id, a, b);
		}

		for (int i = 1; i <= m; i++)
			printf("%d\\n", ans[i]);
	}
}

换皮题:作业


第二场1004 I love counting

传送门: I love counting
题意:

  • 一组数据,第一行 n ( n ≤ 105 ) 代表序列长度,第二行n个数字 ci ( 0 ≤ ci ≤ n ),第三行Q( Q ≤ 105 )代表询问个数,接下来Q行 L , R , a , b ( 1 ≤ L ≤ R ≤ n , a ≤ n + 1 , b ≤ n + 1 )问 序列 L ~ R 中 满足 ci ^ a ≤ b 的数值种类

思路:

  • 前两道题的综合题,位运算从高位向低位遍历,对于每一位都计算该位满足的区间中的数值种类个数,则转换为了区间计数问题即1010题。详细讲解: 移步
  • 具体操作看代码注释。

AC代码(赛后提交1268ms)

#include <bits/stdc++.h>
#define x first
#define y second
#define ls u<<1
#define rs u<<1|1
#define debug(x) cout<<"debug"<<' '<<x<<endl;
using namespace std;
const int N = 1000010;
const int P = 13331;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int>PII;

int k, t, n, m;
int w[N], ans[N], cnt[N], tot[N];
int len, leny;

struct query {
	int l, r, a, b, id;
} q[N];

int get(int x) { //返回x轴分块
	return x / len;
}

int gety(int x) { //返回y轴分块
	return x / sqrt(N);
}

bool cmp(query x, query y) {
	if (get(x.l) == get(y.l)) {
		if (get(x.l) & 1)
			return x.r < y.r;
		else
			return x.r > y.r;
	}
	return get(x.l) < get(y.l);
}

void add(int x) {
	if (!cnt[x])
		tot[gety(x)]++;//tot记录这个块中拥有的不同数值种类数量
	cnt[x]++;//cnt记录该数值出现总次数
}

void del(int x) {
	cnt[x]--;
	if (!cnt[x])
		tot[gety(x)]--;
}

void get_ans(int id, int l, int r) {
	//分块板子
	int res = 0;
	if (gety(l) == gety(r)) {
		for (int i = l; i <= r; i++)
			if (cnt[i])
				res++;
	} else {
		int i = l, j = r;
		while (gety(i) == gety(l)) {
			if (cnt[i])
				res++;
			i++;
		}
		while (gety(j) == gety(r)) {
			if (cnt[j])
				res++;
			j--;
		}
		for (int k = gety(i); k <= gety(j); k++)
			res += tot[k];
	}

	ans[id]以上是关于2021杭电多校中超联赛第一二场莫队Trie树题汇总题解的主要内容,如果未能解决你的问题,请参考以下文章

2021杭电多校中超联赛第一二场莫队Trie树题汇总题解

2021杭电多校中超联赛第一二场莫队Trie树题汇总题解

2021杭电多校赛2021“MINIEYE杯”中国大学生算法设计超级联赛签到题15869

2021杭电多校赛2021“MINIEYE杯”中国大学生算法设计超级联赛签到题3题

2019 杭电多校 第二场

6983 杭电多校(2021“MINIEYE杯”中国大学生算法设计超级联赛3) [记忆化搜索]