您的位置:首页 > 运维架构

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

2017-10-16 12:46 531 查看

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了多次才意识到。。。。
*/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息