两道题都是树上异或最大值的问题,这一类问题都是可持久化01字典树的模板题,不过需要一些树论知识来处理一下。
HDU - 4757:
求树上两点路径间的异或最大值。
显而易见,两点路径就是个lca。
对每一个节点,以其父亲为last版本,新建可持久化字典树即可。
最后用(sz[next[now][!d]] + sz[next[now2][!d]]) - (2 * sz[next[now3][!d]])是否大于0来判断下一步往哪边走,这是一个lca中常见的容斥操作。
这样会漏掉考虑lca(减掉了),所以在最后单独考虑。
lca使用倍增实现。
ac代码:
#include
using namespace std;
const int maxn = 1e5 + 5;
vector<int> G[maxn];
int n, m, num[maxn];
struct Trie {
int tot;
int next[maxn * 30][2], root[maxn];
int sz[maxn * 30];
int p[maxn][20], dep[maxn];
int newNode() {
next[tot][0] = next[tot][1] = 0;
return tot++;
}
void insert(int u, int fa, int x) {
int now = root[u], last = root[fa];
for(int i = 16; i >= 0; i--) {
int d = (x >> i) & 1;
next[now][d] = newNode();
next[now][!d] = next[last][!d];
now = next[now][d];
last = next[last][d];
sz[now] = sz[last] + 1;
}
}
void dfs(int u, int fa) {
root[u] = newNode();
insert(u, fa, num[u]);
p[u][0] = fa;
dep[u] = dep[fa] + 1;
for(int i = 0; i < G[u].size(); i++) {
int v = G[u][i];
if(v == fa) {
continue;
}
dfs(v, u);
}
}
int lca(int u, int v) {
if(dep[u] > dep[v]) swap(u, v);
for(int i = 0; i < 20; i++) {
if((dep[v] - dep[u]) >> i & 1) {
v = p[v][i];
}
}
if(v == u) return u;
for(int i = 20 - 1; i >= 0; i--) {
if(p[u][i] != p[v][i]) {
u = p[u][i];
v = p[v][i];
}
}
return p[u][0];
}
void build() {
tot = 1;
dfs(1, 0);
for(int i = 1; i < 20; i++) {
for(int j = 1; j <= n; j++) {
p[j][i] = p[p[j][i - 1]][i - 1];
}
}
}
int query(int u, int v, int x) {
int k = lca(u, v);
int ans = 0;
int now = root[u], now2 = root[v], now3 = root[k];
for(int i = 16; i >= 0; i--) {
int d = (x >> i) & 1;
int check = (sz[next[now][!d]] + sz[next[now2][!d]]) - (2 * sz[next[now3][!d]]);
if(check > 0) {
ans += (1 << i);
d = !d;
}
now = next[now][d];
now2 = next[now2][d];
now3 = next[now3][d];
}
return max(ans, x ^ num[k]);
}
} trie;
/*
11 99
2 5 5 7 1 9 6 4 2 2 4
1 2
2 3
1 4
4 5
5 8
8 9
4 6
6 7
9 10
9 11
*/
int main() {
while(~scanf("%d%d", &n, &m)) {
for(int i = 1; i <= n; i++) {
scanf("%d", &num[i]);
G[i].clear();
}
int u, v;
for(int i = 1; i <= n - 1; i++) {
scanf("%d%d", &u, &v);
G[u].push_back(v);
G[v].push_back(u);
}
trie.build();
int x, y, z;
while(m--) {
scanf("%d%d%d", &x, &y, &z);
printf("%d\n", trie.query(x, y, z));
}
}
return 0;
}
HDU - 6191 :
这题是求所有子节点的异或最大值。
呃,那又很显然,这时候自然要上把树拍成区间的dfs序了。
求完dfs序之后普通的建可持久化字典树,再普通的像主席树一样做树上差分就完事了。
这题除了写出一个初始化bug找了一年以外打的很顺利。
ac代码:
#include
using namespace std;
const int maxn = 1e5 + 5;
vector<int> G[maxn];
int n, m, num[maxn];
struct Trie {
int tot, clock;
int root[maxn], next[maxn * 32][2];
int sz[maxn * 32];
int in[maxn], out[maxn], tid[maxn];
int newNode() {
next[tot][0] = next[tot][1] = 0;
return tot++;
}
void insert(int now, int last, int x) {
for(int i = 30; i >= 0; i--) {
int d = (x >> i) & 1;
next[now][d] = newNode();
next[now][!d] = next[last][!d];
now = next[now][d];
last = next[last][d];
sz[now] = sz[last] + 1;
}
}
void dfs(int u) {
in[u] = ++clock;
tid[in[u]] = u;
for(int i = 0; i < G[u].size(); i++) {
dfs(G[u][i]);
}
out[u] = clock;
}
void init() {
clock = 0;
tot = 1;
dfs(1);
for(int i = 1; i <= n; i++) {
root[i] = newNode();
insert(root[i], root[i - 1], num[tid[i]]);
}
}
int query(int u, int x) {
int l = root[in[u] - 1], r = root[out[u]];
int ans = 0;
for(int i = 30; i >= 0; i--) {
int d = (x >> i) & 1;
int check = sz[next[r][!d]] - sz[next[l][!d]];
if(check > 0) {
ans += (1 << i);
d = !d;
}
l = next[l][d];
r = next[r][d];
}
return ans;
}
} trie;
/*
9 99
1 1 1 1 1 1 1 1 1
1 2 1 4 4 6 6 8
*/
int main() {
while(~scanf("%d%d", &n, &m)) {
for(int i = 1; i <= n; i++) {
scanf("%d", &num[i]);
G[i].clear();
}
int fa;
for(int i = 2; i <= n; i++) {
scanf("%d", &fa);
G[fa].push_back(i);
}
trie.init();
int u, x;
while(m--) {
scanf("%d%d", &u, &x);
printf("%d\n", trie.query(u, x));
}
}
return 0;
}
打完了题感觉自己的可持久化trie板子有点慢,明天去扒个快的板子下来。
发现一个非常有意思的事实。
next数组里存的索引很有可能跟sz数组有某种等价关系…
把上面两份代码里的sz数组相关全部删掉之后竟然都ac了。