题目描述
题目链接
题解
写在前面
弋(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