BZOJ-4811: [Ynoi2017]由乃的OJ (树链剖分 线段树维护区间操作值 好题)

4811: [Ynoi2017]由乃的OJ

Time Limit: 6 Sec   Memory Limit: 256 MB
Submit: 366   Solved: 118
[ Submit][ Status][ Discuss]

Description

由乃正在做她的OJ。现在她在处理OJ上的用户排名问题。OJ上注册了n个用户,编号为1~",一开始他们按照编号
排名。由乃会按照心情对这些用户做以下四种操作,修改用户的排名和编号:然而由乃心情非常不好,因为Deus天
天问她题。。。因为Deus天天问由乃OI题,所以由乃去学习了一下OI,由于由乃智商挺高,所以OI学的特别熟练她
在RBOI2016中以第一名的成绩进入省队,参加了NOI2016获得了金牌保送
Deus:这个题怎么做呀?
yuno:这个不是NOI2014的水题吗。。。
Deus:那如果出到树上,多组链询问,带修改呢?
yuno:诶。。。???
Deus:这题叫做睡觉困难综合征哟~
虽然由乃OI很好,但是她基本上不会DS,线段树都只会口胡,比如她NOI2016的分数就是100+100+100+0+100+100。
。。NOIP2017的分数是100+0+100+100+0+100所以她还是只能找你帮她做了。。。
给你一个有n个点的树,每个点的包括一个位运算opt和一个权值x,位运算有&,l,^三种,分别用1,2,3表示。
每次询问包含三个数x,y,z,初始选定一个数v。然后v依次经过从x到y的所有节点,每经过一个点i,v就变成v opti
 xi,所以他想问你,最后到y时,希望得到的值尽可能大,求最大值?给定的初始值v必须是在[0,z]之间。每次修
改包含三个数x,y,z,意思是把x点的操作修改为y,数值改为z

Input

第一行三个数n,m,k。k的意义是每个点上的数,以及询问中的数值z都 <2^k。之后n行
每行两个数x,y表示该点的位运算编号以及数值
之后n - 1行,每行两个数x,y表示x和y之间有边相连
之后m行,每行四个数,Q,x,y,z表示这次操作为Q(1位询问,2为修改),x,y,z意义如题所述
0 <= n , m <= 100000 , k <= 64

Output

对于每个操作1,输出到最后可以造成的最大刺激度v

Sample Input

5 5 3
1 7
2 6
3 7
3 6
3 1
1 2
2 3
3 4
1 5
1 1 4 7
1 1 3 5
2 1 1 3
2 3 3 3
1 1 3 2

Sample Output

7
1
5


#include <bits/stdc++.h>
using namespace std;
#define maxn 100005
vector<vector<int> >g(maxn);
struct point{
	int op;
	unsigned long long v;
}a[maxn];
struct tree{
	unsigned long long v0, v1;
	tree() {};
	friend tree operator + (tree x, tree y){    //重载+方便后面合并操作
		tree z;
		z.v0 = (x.v0 & y.v1) | ((~x.v0) & y.v0);
		z.v1 = (x.v1 & y.v1) | ((~x.v1) & y.v0);
		return z;
	}
	tree (int op, unsigned long long v){  //构造函数
		if(op == 1){
			v0 = 0 & v;
			v1 = (~0) & v;
		}
		if(op == 2){
			v0 = 0 | v;
			v1 = (~0) | v;
		}
		if(op == 3){
			v0 = 0 ^ v;
			v1 = (~0) ^ v;
		}
	}
}cl[maxn << 2], cr[maxn << 2]; //因为从结点x走到y,链剖后的分段路径会有从上到下和从下到上的区别,这里需要区分
int sz[maxn], id[maxn], dep[maxn], son[maxn], top[maxn],  pre[maxn], val[maxn], tot;
void dfs(int x, int fa, int d){
	sz[x] = 1;
	pre[x] = fa;
	dep[x] = d;
	son[x] = 0;
	int cur;
	for(int i = 0; i < g[x].size(); ++i){
		cur = g[x][i];
		if(cur == fa) continue;
		dfs(cur, x, d + 1);
		sz[x] += sz[cur];
		if(sz[son[x]] < sz[cur]){
			son[x] = cur;
		}
	}
}
void dfs1(int x, int tp){
	id[x] = ++tot;
	top[x] = tp;
	if(son[x]) dfs1(son[x], tp);
	int cur;
	for(int i = 0; i < g[x].size(); ++i){
		cur = g[x][i];
		if(cur == pre[x] || cur == son[x]) continue;
		dfs1(cur, cur);
	}
}
void build(int o, int l, int r){
	if(l == r){
		cl[o] = cr[o] = tree(a[val[l]].op, a[val[l]].v);
		return;
	}
	int mid = l + r >> 1;
	build(o << 1, l, mid);
	build(o << 1 | 1, mid + 1, r);
	cl[o] = cl[o << 1] + cl[o << 1 | 1];
	cr[o] = cr[o << 1 | 1] + cr[o << 1];   //合并上的区别,值得注意
}
void add(int o, int l, int r, int pos, int op, unsigned long long z){
	if(l == r){
		cl[o] = cr[o] = tree(op, z);
		return;
	}
	int mid = l + r >> 1;
	if(pos <= mid) add(o << 1, l, mid, pos, op, z);
	else add(o << 1 | 1, mid + 1, r, pos, op, z);
	cl[o] = cl[o << 1] + cl[o << 1 | 1];   //注意合并的顺序
	cr[o] = cr[o << 1 | 1] + cr[o << 1]; 
}
tree query(int o, int l, int r, int L, int R, int kind){
	if(l >= L && r <= R){
		if(kind == 1) return cl[o];
		return cr[o];
	}
	int mid = l + r >> 1;
	if(mid >= R) return query(o << 1, l, mid, L, R, kind);
	else if(mid < L) return query(o << 1 | 1, mid + 1, r, L, R, kind);
	else{
		if(kind == 1) return query(o << 1, l, mid, L, R, kind) + query(o << 1 | 1, mid + 1, r, L, R, kind);
		else return query(o << 1 | 1, mid + 1, r, L, R, kind) + query(o << 1, l, mid, L, R, kind);    //注意合并的顺序
	}
}
unsigned long long getsm(int x, int y, unsigned long long z, int k){
	int tp1 = top[x], tp2 = top[y];
	tree ansx = tree(3, 0), ansy = tree(3, 0);
	while(tp1 != tp2){
		if(dep[tp1] < dep[tp2]){
			ansy = query(1, 1, tot, id[tp2], id[y], 1) + ansy;   //还是注意顺序。。。。
			y = pre[tp2];
			tp2 = top[y];
		}
		else{
			ansx = ansx + query(1, 1, tot, id[tp1], id[x], 2);
			x = pre[tp1];
			tp1 = top[x];   
		}
	}
	if(dep[x] < dep[y]){
		ansx = ansx + query(1, 1, tot, id[x], id[y], 1) + ansy;   //注意合并的顺序
	}
	else{
		ansx = ansx + query(1, 1, tot, id[y], id[x], 2) + ansy;
	}
	unsigned long long res = 0, cost = 0;
	for(int i = k - 1; i >= 0; --i){   //这里一定全程unsigned long long!!!!
		if((ansx.v0 >> i) & 1ULL * 1){
			res |= 1ULL * 1 << i;
		}
		else if((ansx.v1 >> i) & 1ULL * 1){
			if((cost | (1ULL * 1 << i)) <= z){
				res |= 1ULL * 1 << i;
				cost |= 1ULL * 1 << i;
			}
		}
	}
	return res;
}
int main(){
	int n, m, k, u, v, q;
	unsigned long long z;
	scanf("%d %d %d", &n, &m, &k);
	for(int i = 1; i <= n; ++i){
		scanf("%d %llu", &a[i].op, &a[i].v);
	}
	for(int i = 1; i < n; ++i){
		scanf("%d %d", &u, &v);
		g[u].push_back(v);
		g[v].push_back(u);
	}
	tot = 0;
	dfs(1, 0, 1);
	dfs1(1, 1);
	for(int i = 1; i <= n; ++i){
		val[id[i]] = i;
	}
	build(1, 1, tot);
	for(int i = 1; i <= m; ++i){
		scanf("%d %d %d %llu", &q, &u, &v, &z);
		if(q == 2){
			add(1, 1, tot, id[u], v, z);
		}
		else{
			printf("%llu\n", getsm(u, v, z, k));
		}
	}
}

/*
题意:一棵树,1e5个结点,每个结点一个操作(&,|,^)和一个数值。1e5次操作 ,每次操作要么修改每个结点的操作和
数值,要么询问路径x到y上,在[0,z]中找个数对和每个结点上的数字进行一次该结点上的操作,使得最终答案最大。

思路:一开始想可以直接把操作和数字合并啊,然后直接树链剖分然后线段树维护区间值,然后发现naive了,&,|,^这三个
位运算没办法用一个数搞定,那么就用两个数好了。。。。v0,v1分别状压(这三种位运算是位独立的,位和位互不影响)表示每一位 
原本是0经过操作后得到什么,和原本是1经过操作后得到什么。所以操作&和数字可以合并为v0 = 0 & v; v1 = (~0) & v, 其余的类推,然后我们
就不用管操作符了。之后就可以用线段树维护区间的值了,这里我们得讨论合并操作,就是v0在经过x,再经过y后怎么表示。
首先v0在经过x后,有些位变成1了,这个时候我们用v1.y更新这些位,很容易理解吧,即 (x.v0 & y.v1);还有一些位没有变成0,还
保持着0怎么办,当然用v0.y更新这些位了- -||.即((~x.v0) & y.v0),x.v0取非的原因是还保持0的那些位直接&的话一定是0,无法体现
y.v0的作用,实际上这里如果单独考虑还是0的位直接用y.v0就好了,但是避免不是0的位的影响,所以用~x.v0,这样非0的变成0,不考虑这些位,
为0的变成1,和y.v0 &,保留y.v0对这些位的贡献。然后 | 一下就好了,合并两者各自位的贡献。

注意:从x到y后操作的先后顺序,这样我们需要区分合并区间的顺序,这个很简单,用两个数组存就好了,合并的时候注意一下。然后由于最大64
位,全程都用unsigned long long!!!WA了多次才意识到。。。。
*/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值