题目大意:在一棵带点权的有根树上有两种操作:一是修改某个点的权值,二是询问一个点与它到根节点路径上的某个点可异或得到的最大值。
一开始想到树上可持久化 trie,但是带修改操作的话每一次修改都得修改它的所有子树。
既然修改一个点会影响到它的子树的答案,考虑按 dfs 序来维护一棵线段树,每次将修改更新到它的 dfs序区间。
要使得异或上某个值最大,需要用到 01字典树,可以线段树的每一个结点维护一棵 01字典树,每次结点修改权值,在它的 dfs 序区间更新字典树即可,一次修改最多拆分出 log 次线段树的区间修改,复杂度是 O ( 30 log n ) O(30 \log n) O(30logn)。
查询有两种方法:
1.查询必定查的是叶子结点,可以在修改时做懒人标记,将更新一路推下来,到叶子结点再做查询,这种方法空间开销非常大。
2.不做懒人标记,在线段树上每一层都做一次字典树的查询,答案取最大值。因为字典树可以分段查询,不需要所有操作都更新在一个节点再做查询,这样做不会影响答案。
考虑第二种做法,但是在线做法会MLE。
考虑离线:从第二种做法可以看出,对线段树的每个结点,只要按时间顺序把修改区间覆盖这个结点区间的操作做一遍,也不会影响答案。
考虑离线用线段树维护操作序列,每一个结点维护覆盖这个区间的更新操作,对于查询操作需要从根节点到叶子结点一路插入 log 个(第二种查询方法)。最后遍历整颗线段树,每次进入该节点时清空 trie,将该结点的操作依次维护到一棵trie上。这样只需要维护一棵 trie,空间复杂度为 O ( 30 n ) O(30 n) O(30n),时间复杂度为 O ( 30 n log n ) O(30 n \log n) O(30nlogn)。
具体见代码:
代码:
#include
using namespace std;
const int maxn = 1e5 + 10;
int t,n,m,ans[maxn];
struct node {
int op,id,x,v;
node(int op = 0,int id = 0,int x = 0,int v = 0) {
this -> op = op;
this -> id = id;
this -> x = x;
this -> v = v;
}
};
struct trie{ //01字典树
int sz,son[maxn * 30][2],sum[maxn * 30];
void init() {
sz = 0;
sum[0] = son[0][0] = son[0][1] = 0;
}
void upd(int v,int d) { //类似主席树的更新,每一步都要更新一个结点
int rt = 0;
for(int i = 30; i >= 0; i--) {
int p = (v >> i) & 1;
if(!son[rt][p]) {
son[rt][p] = ++sz;
sum[sz] = son[sz][0] = son[sz][1] = 0;
}
sum[son[rt][p]] += d;
rt = son[rt][p];
}
}
int qry(int v) {
int ans = 0,rt = 0;
for (int i = 30; i >= 0; i--) {
int p = (v >> i) & 1;
if(sum[son[rt][p ^ 1]] > 0) {
ans |= (1 << i);
rt = son[rt][p ^ 1];
} else {
rt = son[rt][p];
}
}
return ans;
}
}trie;
struct seg_tree { //线段树
#define lson rt << 1,l,mid
#define rson rt << 1 | 1,mid + 1,r
vector<node> opt[maxn << 2];
void build(int rt,int l,int r) {
opt[rt].clear();
if(l == r) return;
int mid = l + r >> 1;
build(lson);build(rson);
}
void insert_upd(int L,int R,node tp,int rt,int l,int r) { //更新操作的插入
if(L <= l && r <= R) {
opt[rt].push_back(tp);
return ;
}
int mid = l + r >> 1;
if(L <= mid) insert_upd(L,R,tp,lson);
if(mid + 1 <= R) insert_upd(L,R,tp,rson);
}
void insert_qry(int p,node tp,int rt,int l,int r) { //查询操作的插入
opt[rt].push_back(tp);
if(l == r) return ;
int mid = l + r >> 1;
if(p <= mid) insert_qry(p,tp,lson);
else insert_qry(p,tp,rson);
}
void solve(int rt,int l,int r) {
trie.init();
for(auto it : opt[rt]) {
if(it.op == 0) {
trie.upd(it.x,it.v);
} else {
ans[it.id] = max(ans[it.id],trie.qry(it.x));
}
}
if(l == r) return ;
int mid = l + r >> 1;
solve(lson); solve(rson);
}
}seg_tree;
vector<int> g[maxn],tmp;
int val[maxn],st[maxn],ed[maxn],cnt;
void init() {
tmp.clear();
for(int i = 1; i <= n; i++) {
g[i].clear();
val[i] = st[i] = ed[i] = 0;
}
for(int i = 1; i <= m; i++) {
ans[i] = 0;
}
cnt = 0;
}
void dfs(int u,int fa) {
st[u] = ++cnt;
for(auto it : g[u]) {
if(it == fa) continue;
dfs(it,u);
}
ed[u] = cnt;
}
int main() {
scanf("%d",&t);
while(t--) {
scanf("%d%d",&n,&m);
init();
for(int i = 2,x; i <= n; i++) {
scanf("%d",&x);
g[x].push_back(i);
g[i].push_back(x);
}
dfs(1,0);
seg_tree.build(1,1,n);
for(int i = 1; i <= n; i++) {
scanf("%d",&val[i]);
seg_tree.insert_upd(st[i],ed[i],node(0,0,val[i],1),1,1,n);
}
for(int i = 1; i <= m; i++) {
int op,x,y;
scanf("%d",&op);
if(op == 0) {
scanf("%d%d",&x,&y);
seg_tree.insert_upd(st[x],ed[x],node(0,i,val[x],-1),1,1,n);
val[x] = y;
seg_tree.insert_upd(st[x],ed[x],node(0,i,val[x],1),1,1,n);
} else {
scanf("%d",&x);
seg_tree.insert_qry(st[x],node(1,i,val[x],0),1,1,n);
tmp.push_back(i);
}
}
seg_tree.solve(1,1,n);
for(int i = 0; i < tmp.size(); i++) {
printf("%d\n",ans[tmp[i]]);
}
}
return 0;
}