洛谷题单指南-字符串-P4592 [TJOI2018] 异或
原题链接:https://www.luogu.com.cn/problem/P4592
题意解读:在一定范围内,查找一个值与z异或最大,依然是一个区间异或问题,直觉上可以使用持久化Trie。
解题思路:
设seq[i]表示节点i在dfs过程的顺序号siz[i]表示节点i为根的子树大小,depth[i]表示节点i所在的深度;
对于op = 1,在x的子树中查找与z异或最大值,
由于树在前序dfs过程中,x的子树所有节点顺序号一定是升序的,因此可以在dfs(先根)过程中,针对当前节点顺序号以及前一个顺序号建立Trie,记为trie1;对于x的子树范围,在顺序号中就是一段连续的内容,起点是seq[x],终点是seq[x] + siz[x] - 1。
对于op = 2,在x、y的路径中查找与z异或最大值,
可以借助于计算LCA(x,y),再分别在seq[x] ~ seq[LCA(x,y)],seq[y] ~ seq[LCA(x,y)]范围查找与z异或最大值,再求两者之间的最大值,因此可以在dfs过程中,针对当前节点顺序号以及父节点顺序号建立Trie,记为trie2;
可以看出,此题综合性非常强,需要以下前置知识作为基础:
1、邻接表建图建树
2、树的先序DFS,并记录节点顺序号
3、在DFS中,统计节点所在子树的大小
4、在DFS中,计算节点的深度,用于LCA
5、倍增法求LCA,以及在DFS中递推计算fa
6、在持久化Trie中添加节点
7、在持久化Trie中查找区间异或最大值
下面就结合代码,在注释中详细阐明以上内容。
100分代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 100005;
int n, q;
vector<int> tree[N]; //领接表
int w[N], seq[N], seqno, siz[N], depth[N]; //w:节点权值,seq:dfs前序编号,siz:子树大小,depth:深度
int root1[N], root2[N]; //记录trie树的根节点
int trie1[N * 32][2], trie2[N * 32][2], idx1, idx2, maxid1[N * 32], maxid2[N * 32]; //trie树以及经过每个节点最大的数的编号
int fa[N][20]; //fa[i][j]表示从i节点向上跳2^j所到达的节点
//建立trie1
void add1(int id)
{
int sq = seq[id]; //获取顺序号
root1[sq] = ++idx1; //建立根节点
int cur = root1[sq]; //当前根节点
int pre = root1[sq - 1]; //前一个根节点
for(int i = 30; i >= 0; i--)
{
int v = w[id] >> i & 1;
trie1[cur][!v] = trie1[pre][!v];
trie1[cur][v] = ++idx1;
maxid1[cur] = sq;
cur = trie1[cur][v], pre = trie1[pre][v];
}
maxid1[cur] = sq;
}
//建立trie2
void add2(int id, int fid)
{
int sq = seq[id], fsq = seq[fid]; //获取顺序号
root2[sq] = ++idx2; //建立根节点
int cur = root2[sq]; //当前根节点
int pre = root2[fsq]; //前一个根节点
for(int i = 30; i >= 0; i--)
{
int v = w[id] >> i & 1;
trie2[cur][!v] = trie2[pre][!v];
trie2[cur][v] = ++idx2;
maxid2[cur] = sq;
cur = trie2[cur][v], pre = trie2[pre][v];
}
maxid2[cur] = sq;
}
//在trie1中l~r范围内查找与val异或最大结果
int find1(int l, int r, int val)
{
int res = 0;
int u = root1[r]; //根,先在1~r范围查找
for(int i = 30; i >= 0; i--)
{
int v = val >> i & 1;
if(maxid1[trie1[u][!v]] >= l) //如果相反位的节点存在且最大编号>=l,限定范围l~r
{
u = trie1[u][!v];
res = res * 2 + 1; //异或后1对结果的贡献
}
else
{
u = trie1[u][v];
res = res * 2; //异或后0对结果的贡献
}
}
return res;
}
//在trie2中l~r范围内查找与val异或最大结果
int find2(int l, int r, int val)
{
int res = 0;
int u = root2[r]; //根,先在1~r范围查找
for(int i = 30; i >= 0; i--)
{
int v = val >> i & 1;
if(maxid2[trie2[u][!v]] >= l) //如果相反位的节点存在且最大编号>=l,限定范围l~r
{
u = trie2[u][!v];
res = res * 2 + 1; //异或后1对结果的贡献
}
else
{
u = trie2[u][v];
res = res * 2; //异或后0对结果的贡献
}
}
return res;
}
//从根u,父节点f开始dfs
void dfs(int u, int f)
{
seq[u] = ++seqno; //遍历顺序号,前序
add1(u); //以dfs前序关系建立trie1
add2(u, f); //以父子关系建立trie2
depth[u] = depth[f] + 1; //更新深度
siz[u] = 1; //u为根的子树初始只有他自己
fa[u][0] = f; //从u往上跳2^0步到f
for(int i = 1; i < 20; i++) fa[u][i] = fa[fa[u][i - 1]][i - 1]; //递推计算fa
for(int v : tree[u])
{
if(v == f) continue; //避免往回走
dfs(v, u);
siz[u] += siz[v]; //u为根的子树大小是所有子树的大小之和
}
}
int lca(int x, int y)
{
if(depth[x] < depth[y]) swap(x, y);
for(int i = 19; i >= 0; i--)
{
if(depth[fa[x][i]] >= depth[y])
{
x = fa[x][i];
}
}
if(x == y) return x;
for(int i = 19; i >= 0; i--)
{
if(fa[x][i] != fa[y][i])
{
x = fa[x][i];
y = fa[y][i];
}
}
return fa[x][0];
}
int main()
{
cin >> n >> q;
for(int i = 1; i <= n; i++) cin >> w[i]; //读取权值
for(int i = 1; i < n; i++) //双向建图
{
int u, v;
cin >> u >> v;
tree[u].push_back(v);
tree[v].push_back(u);
}
dfs(1, 0);
while(q--)
{
int op, x, y, z;
cin >> op;
if(op == 1)
{
cin >> x >> z;
cout << find1(seq[x], seq[x] + siz[x] - 1, z) << endl;
}
else
{
cin >> x >> y >> z;
int xy = lca(x, y);
cout << max(find2(seq[xy], seq[x], z), find2(seq[xy], seq[y], z)) << endl;
}
}
}