9.12 数据结构测试 题解

9.12 数据结构测试 题解

A. 敌兵布阵(线段树 / 树状数组)

线段树 / 树状数组模板。

B. 回文串(Manacher 算法)

Manacher 算法模板。

C. 异或区间(01-Trie)

题意

给出一个 \(n\) 个自然数的序列,求一个区间,使得这个区间里的所有数的异或和最大,输出求得的异或和。

\(n\le10^5,1\le a_i\le10^9\)

思路

设前缀异或和为 \(sum\)。遍历一遍序列,对于每一个 \(i\),求出 \(j\in[1,i]\) 使得 \(sum_i\oplus sum_j\) 最大。

01-Trie 可以实现在一些数中查找与某个数 \(x\) 异或结果最大的那个数,并可以返回这个结果。

遍历时先查询 \(\max\limits_{1\le j\le i}\{sum_i\oplus sum_j\}\) 并更新答案,然后将 \(sum_i\) 加入 01-Trie。

注意

  • 开始时需要将 \(0\) 加入 01-Trie,否则会被特殊数据卡掉(某一前缀为最大异或和序列的情况);

  • 01-Trie 的数组大小需要开到 \(N\times30\)

代码

#include <cstdio>
#include <iostream>
#define f(x, y, z) for (int x = (y); (x) <= (z); ++(x))
#define g(x, y, z) for (int x = (y); (x) >= (z); --(x))
#define il inline
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
int n, x, sum, ans;

struct Trie{
	
	int tr[N * 30][2], tot;
	
	il void insert(int x) {
		int u = 0;
		g(i, 30, 0) {
			int c = (x >> i) & 1;
			if (!tr[u][c]) tr[u][c] = ++tot;
			u = tr[u][c];
		}
		return;
	}
	
	il int query(int x) {
		int u = 0, res = 0;
		g(i, 30, 0) {
			int c = (x >> i) & 1;
			if (tr[u][c ^ 1]) u = tr[u][c ^ 1], res |= (1 << i);
			else u = tr[u][c];
		}
		return res;
	}
	
} tr;

signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
//	freopen("C2.in", "r", stdin);
	
	cin >> n;
	tr.insert(0);
	f(i, 1, n) {
		cin >> x;
		sum ^= x;
		ans = max(ans, tr.query(sum));
		tr.insert(sum);
	}
	cout << ans << '\n';
	
	return 0;
}

D. 子树(树综合)

题意

给定一棵大小为 \(n\)、开始时根为 \(1\) 的树,操作 \(Q\) 次,有两种操作:

  • 如果 \(\text{type}\)\(1\) 代表这是一次换根操作,后接一个整数 \(x\ (1\le x\le n)\) 表示树根变为 \(x\)
  • 如果 \(\text{type}\)\(2\) 代表这是一次查询操作,后接一个整数 \(x\ (1\le x\le n)\) 表示询问在当前根下 \(x\) 的子树大小。

\(1\le n\le 10^5,1\le Q\le 10^5\)

思路

首先在根为 \(1\) 的情况下,处理出以 \(i\) 为根的子树大小 \(siz_i\)

对于每一次查询:

  • 如果 \(x=rt\),那么答案为 \(n\)​;

  • 如果当前的根 \(rt\) 不在(换根前)以 \(x\) 为根的子树内,那么对以 \(x\) 为根的子树大小没有影响,所以答案为 \(siz_x\)

  • 如果当前的根 \(rt\) 在(换根前)以 \(x\) 为根的子树内,那么换根前 \(rt\) 所在的 \(x\) 的子树即为换根后 \(x\) 的父亲,其他节点都在(换根后)以 \(x\) 为根的子树中,所以答案为 \(n-siz_{son_x}\),其中 \(son_x\) 为换根前 \(x\) 的第一代儿子且为 \(rt\) 的祖先。

为了判断当前的根 \(rt\) 是否在(换根前)以 \(x\) 为根的子树内,我们需要处理出以 \(x\) 为根的子树中的所有节点的 dfn 序号的范围(代码中以 dmindmax 表示)。

为了求出 \(son_x\),我们需要倍增在 \(rt\) 的祖先中找 \(son_x\),最后保证 \(dep_{son_x}=dep_x+1\),所以需要处理出倍增数组和深度数组。

代码

#include <cstdio>
#include <iostream>
#define f(x, y, z) for (int x = (y); (x) <= (z); ++(x))
#define g(x, y, z) for (int x = (y); (x) >= (z); --(x))
#define il inline
using namespace std;
const int N = 1e5 + 10;
int n, Q;

int head[N], cnt;
struct Edge{
	int to, nxt;
} e[N << 1];

il void add(int from, int to) {
	e[++cnt].to = to, e[cnt].nxt = head[from], head[from] = cnt;
	return;
}

int rt = 1, fa[N][25], siz[N], dmin[N], dmax[N], tot, dep[N];

void DFS(int u, int f) {
	fa[u][0] = f;
	siz[u] = 1;
	dep[u] = dep[f] + 1;
	for (int i = 0; fa[u][i]; ++i) fa[u][i + 1] = fa[fa[u][i]][i];
	dmin[u] = dmax[u] = ++tot;
	for (int i = head[u]; i; i = e[i].nxt) {
		int to = e[i].to;
		if (to == f) continue;
		DFS(to, u);
		dmax[u] = dmax[to];
		siz[u] += siz[to];
	}
	return;
}

int Find(int x, int root) {
	g(i, 17, 0)
		if (dep[fa[root][i]] > dep[x]) root = fa[root][i];
	return root;
}

il int Query(int x) {
	if (x == rt) return n;
	if (dmin[rt] < dmin[x] || dmin[rt] > dmax[x]) return siz[x];
	return n - siz[Find(x, rt)];
}

signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
//	freopen("D1.in", "r", stdin);
//	freopen("D1.out", "w", stdout);
	
	cin >> n;
	f(i, 1, n - 1) {
		int x, y;
		cin >> x >> y;
		add(x, y), add(y, x);
	}
	DFS(1, 0);
	cin >> Q;
	while (Q--) {
		int type, x;
		cin >> type >> x;
		if (type == 1) rt = x;
		else if (type == 2) cout << Query(x) << '\n';
	}
	
	return 0;
}
posted @ 2022-11-06 20:32  f2021ljh  阅读(13)  评论(0)    收藏  举报