题解 弋或树

题目描述

题目链接

题解

写在前面

弋(yì)或树 异或树

前置知识

为了解决本题,你应该要会

  • Trie树模板
  • Trie树二进制表示数

思路

解法一

我会暴力!
最坏情况下时间复杂度为\(O(nm)\)
得分:\(29\)
好水啊

解法water

我会Trie树!
预处理所有子树异或和,加入Trie树,直接二进制查找。
得分:\(0\)
这件事情告诉我们暴力比乱搞好用

解法二

我会修改Trie树模板!

观察本题后发现本题难点在于如何\(O(logx)\)求出以u为根的子树下某一个子树的异或和

思考后发现可以选择在Trie树内记录 Trie树每个节点 所对应的 题目给出的树的节点,再lca\(O(logn)\)查询是否有祖先关系即可

然而当所有子树异或和相同时 Trie树每个节点 所对应的 题目给出的树的节点 可以达到\(n\)个,总的时间复杂度为\(O(m \times logx \times n \times logn)\)
得分:\(29\)

解法三

我会建多个Trie树!
即对于每个节点都建一个Trie树
然而当树退化为链时,建树时间复杂度可以达到\(O(logxn^2)\),空间复杂度为\(O(n^2)\)
得分:\(0\)

解法四

我会离线处理!

对于解法三,我们发现父节点的Trie树其实是由子节点的Trie树决定的

所以说可以考虑先求出叶子节点,再一层一层将Trie树上移
即先求出儿子节点的Trie树,处理完儿子节点的询问,再将其中儿子节点的Trie树拓展到其父亲的Trie树(加入其它子节点即父节点),重复此步骤

这样,当询问的点要么基本覆盖整棵树时,拓展的效率会比较高,当重复较多时,多次处理效率也较高,不过最坏时间复杂度似乎有点难估算

具体算法实现步骤为
(以下将有询问的节点统称为询问节点)

1.预处理
2.找到询问节点u
3.对于每个询问节点u,先找到询问节点u的子树中 子树节点数量最多的询问子节点v
4.并将询问节点u的子树中其他节点加入询问节点v的Trie树中
5.将询问节点u的询问全部处理

代码

#include
#include
#include
#include
#include
using namespace std;
int n,m,a[100005],x[100005],s[100005],d[100005];
struct que {
	int x,id;
} ans[100005];
vectorq[100005];
vectorg[100005];
void dfs(int u,int fa) {//步骤1
	s[u]=1,x[u]=a[u];
	for(int i=0; ic;
	int tot;
	void build() {
		tot=0;
		c.clear(),c.push_back(NULL);
	}
	void insert(int x) {
		int p=0;
		for(int i=30; i>=0; i--) {
			int v=((x&(1<>i);
			if(c[p]==NULL) {
				c[p]=new int[2];
				memset(c[p],-1,sizeof(int)*2);
			}
			if(c[p][v]==-1) {
				c[p][v]=++tot;
				c.push_back(NULL);
			}
			p=c[p][v];
		}
	}
	int find(int x) {
		int p=0;
		for(int i=30; i>=0; i--) {
			int v=((x&(1<>i);
			if(c[p][v^1]==-1)p=c[p][v];
			else p=c[p][v^1],x=(x^(1<0) {
		if(s[u]>s[b])b=u;
		return;
	}
	for(int i=0; i0) {
		b=0;//步骤3
		for(int i=0; i'9')ch=getchar();
	while(ch>='0'&&ch<='9') {
		s=s*10+ch-'0';
		ch=getchar();
	}
	return s;
}
int main() {
	n=read(),m=read();
	for(int i=1; i<=n; i++)a[i]=read();
	for(int i=1; i

你可能感兴趣的:(题解 弋或树)