HDU 5390 :tree(线段树离线分治 + 01字典树)

在这里插入图片描述
题目大意:在一棵带点权的有根树上有两种操作:一是修改某个点的权值,二是询问一个点与它到根节点路径上的某个点可异或得到的最大值。

一开始想到树上可持久化 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<bits/stdc++.h>
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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值