Tire树练习题

发布时间 2023-10-12 14:51:50作者: superl61

Tire树练习题

T1 「一本通 2.3 例 2」The XOR Largest Pair

在给定的 \(N\) 个整数\(A_1,A_2,...A_N\)中选出两个进行异或运算,得到的结果最大是多少?

数据范围

对于 100%的数据,\(1 \leq N \leq 10^5,0 \leq A_i \leq 2^{31}\)

1.朴素算法并用二分查去匹配二进制下每个数最高位的0还是能卡过的

2.结合异或的运算规则,怎样才能使两个数的异或和尽量大呢?答案是尽量让二进制下每一位都不同,如果对于每个数x,能直接找到某一位和它不同的数,那就可以贪心选取二进制下尽量不一样的数了。那怎么找呢?

3.结合数据范围发现每个数写成二进制最多才31位,每一位01两个状态。所以对每个数的二进制建trie树(深度越浅代表的二进制位越高,树的size最大为\(N*31\) ),对于每个数,在trie树上每走一步就贪心地选当前位和它不一样的数

  • 为什么这样贪一定使对的呢?假设当前数的第k位是0,trie树上的第k位存在1,那要么选这个1,则对结果有\(2^k\)的贡献;要么不选1选0,则后面的数位总共对结果最多有\(2^k-1\)的贡献,故第k位选相反数一定使更优的。算法正确性得证。

\(Code:\)

#include<bits/stdc++.h>
using namespace std; 
#define F(i,l,r) for(int i=l;i<=r;++i) 
#define G(i,r,l) for(int i=r;i>=l;--i) 
const int N=1e5+5,M=20;
int n,num=0,mx=-1,z,a[N*M],ch[N*M][2],val[N*M];
inline void insert(int x){
	int p=0;
	G(i,30,0){
		z=x&(1<<i)?1:0;
		if(!ch[p][z]) ch[p][z]=++num;
		p=ch[p][z];
	} val[p]=x;
}
inline void qry(int x){
	int p=0;
	G(i,30,0){
		z=x&(1<<i)?1:0;
		if(ch[p][1^z]) p=ch[p][1^z];
		else if(ch[p][z]) p=ch[p][z];
	}
	mx=max(mx,x^val[p]);
}
int main(){
	scanf("%d",&n);
	F(i,1,n) scanf("%d",&a[i]),insert(a[i]);
	F(i,1,n) qry(a[i]);
	printf("%d",mx);
	return 0;
}

T2「一本通 2.3 练习 5」The XOR-longest Path

给定一棵 \(N\) 个点的带权树,求树上最长的异或和路径

  • 写在前面:审题一定要审清楚!没有指定\(1\)是根,“路径”的意思也不是说一定路径从根节点出发

(不然直接dfs这题不就秒了么)

1.暴力:分别以每个点为根跑根到每个点的异或和,最后取max,时间复杂度\(O(N^2)\),大概能过40分

2.Trie树优化:第一感觉一定是这道题和上面那道题很像,但关键是上题只需两两匹配,容易构建trie树,这道题要选很多条边权出来匹配,想了很久都不知道怎么建trie树。

考虑转化问题

(1)先任选一个点为根比如1,sum[i]表示根到所有点的路径异或和,这个可以一次dfs搞定

(2)接下来要利用一个很NB的性质(其实主要是我以前没见过doge)x ^ x=0,自己过程中还发现一个性质,顺带提一下吧虽然用不上:x^y=z z^y=x(有点像加减法)

(3)这个性质有什么用呢?学了LCA都知道树上任意两点的路径可以转化为 x 到 lca 的路径和 lca 的 y 的路径拼起来

(4)如果这道题询问的是路径上边权的代数和最大,那我们肯定求个lca就做完了。那异或和呢?

(5)考虑把sum[x]和sum[y]展开

展开即每一条边权来异或,由于异或有交换律,发现1到p(即lca(x,y))的边权会和自己异或,于是乎全部都变成了0,只剩下x到p,p到y的边权异或和,这恰恰就是我们想要的,甚至不用求lca

所以路径异或和又转换成了sum数组两两之间异或和的最大值,即T1所求。复杂度\(O(31N)\)

END~