题面

BZOJ题面,比较不清晰

Luogu题面,写的比较清楚

思路

原题目

我们先看这道题的原题目NOI2014起床困难综合症

的确就是上树的带修改版本

那么我们先来解决这个原版的序列上单次询问

二进制的这些操作,我们把操作数和符号一起(比如xor 7,and 31)挪动的话,答案是会改变的,不同符号之间不满足交换律和结合律

那么我们就无法把这些操作的顺序随意变换为比较好算的方式了

考虑二进制位运算的过程,发现这三种运算的过程中,不同的二进制位之间不会有互相影响

因此我们考虑把每个二进制位拆出来处理

对于每一个二进制位,我们记录两个值a0,a1,分别表示这一位上是0或1的情况下,走过所有运算之后得到的是0还是1

然后,我们从高的二进制位往低的走,贪心地在高位可以达成1且不会超过m的限制的情况下,令高位为1

这道题就做完了(的确作为NOI题来说简单了一些,果然签到题啊)

那么当本题上树+修改之后了,怎么办呢?

树剖+线段树

一个显而易见的想法就是用树剖+线段树这种可以轻松维护链上信息的数据结构来修改+询问

那么,我们对于每一个二进制位,维护区间中从左到右跑和从右到左跑得到的是0还是1,询问的时候把链拆开到线段树上,不同的链之间可以直接01对应合并

再和上面一样贪心就好了......吗?

这样的复杂度是多少呢?是$O(nklog2n)$的,虽然那个$log2$常数很小,但是这个玩意儿满打满算也是个$O(nklogn)$的,本题数据范围下肯定过不了

优化

这怎么解决?

我们发现上述复杂度中,最有希望优化掉的就是那个$k$,因为其他的都是树剖的固有复杂度了

怎么优化掉$k$呢?我们考虑再把这些二进制位压回同一个 long long 里面

但是这样就有一个问题,那就是在线段树上,我们不知道怎么合并了

原来每一个二进制位拆开的时候,两个线段树节点之间的合并就是0出来的对应0进去的,1出来的对应1进去的,但是压位起来以后,难道我们要枚举$2^k$种状态分别对应转移吗?

非也

线段树节点的合并

二进制位运算是每一位独立的

我们现在要再一次让这个结论发挥作用:我们可以用位运算来解决这些独立的、但是被压到一起了的二进制位

假设当前要合并的线段树节点是$l$和$r$,$f_0[l]$的第$i$位代表这一位进去是0时出来的值,$f_1[l]$同理

那么显然,对于新的合并后节点$u$而言,如果$f_0[u]$的第$i$位(后文中用$f_0[u][i]$表示)是1,那么有以下两种情况:

情况一:$f_0[l][i]1$且$f_1[r][i]1$

情况二:$f_0[l][i]0$且$f_0[r][i]1$

可以得到表达式:$f_0[u][i]=(f_0[l][i];bitand;f_1[r][i]);bitor;(!f_0[l][i];bitand;f_0[r][i])$

那么把这个式子放到整个$f_0[u]$的角度上,就是$f_0[u]=((f_0[l];bitand;f_1[r]);bitor;((bitnot;f_0[l]);bitand;f_1[r]))$

其中$bitand,bitor,bitnot$分别是按位与,或,取反

对于$f_1[u]$,也是同样的道理,$f_1[u]=((f_1[l];bitand;f_1[r]);bitor;((bitnot;f_1[l]);bitand;f_0[r]))$

(注意还有反过来跑的要处理)

问题解决

这样之后,我们可以对于修改进行单点修改,然后树链剖分+区间询问,得到每一位的输入01对应输出01。树剖中的序列合并也可以用上述方法合并起来

最后我们做一次原题中的贪心即可

坑点

要用unsigned long long

需要维护4个值,不要写混了

树剖得到两个序列:x->lca和y->lca,但是注意其中一个要把跑的顺序反过来

Code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cassert>
#include<cmath>
#define ll unsigned long long
using namespace std;
inline ll read(){
ll re=0,flag=1;char ch=getchar();
while(ch>'9'||ch<'0'){
if(ch=='-') flag=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9') re=(re<<1)+(re<<3)+ch-'0',ch=getchar();
return re*flag;
}
const ll LIM=0-1;
int n,Q,K,first[200010],dep[200010],son[200010],siz[200010],top[200010],pos[200010],back[200010];
int op[200010],cnte,cntn,fa[200010];ll val[200010];
struct data{
ll f0,f1,inv0,inv1;
//f0:从左到右,0进
//f1:从左到右,1进
//inv0:从右到左,0进
//inv1:从右到左,1进
data(){f0=f1=inv0=inv1=0;}
}a[800010];
struct edge{
int to,next;
}e[200010];
inline void add(int u,int v){
e[++cnte]=(edge){v,first[u]};first[u]=cnte;
e[++cnte]=(edge){u,first[v]};first[v]=cnte;
}
void dfs1(int u,int f){
int i,v;dep[u]=dep[f]+1;siz[u]=1;son[u]=0;fa[u]=f;
for(i=first[u];~i;i=e[i].next){
v=e[i].to;if(v==f) continue;
dfs1(v,u);siz[u]+=siz[v];
if(siz[v]>siz[son[u]]) son[u]=v;
}
}
void dfs2(int u,int t){
int i,v;top[u]=t;pos[u]=++cntn;back[cntn]=u;
if(son[u]) dfs2(son[u],t);
for(i=first[u];~i;i=e[i].next){
v=e[i].to;if(pos[v]) continue;
dfs2(v,v);
}
}
ll calc(ll num,int x){
if(op[x]==1) return num&val[x];
if(op[x]==2) return num|val[x];
if(op[x]==3) return num^val[x];
}
data update(data l,data r){
data re;//核心合并过程,参考上方题解
re.f0=((l.f0&r.f1)|((~l.f0)&r.f0));
re.f1=((l.f1&r.f1)|((~l.f1)&r.f0));
re.inv0=((r.inv0&l.inv1)|((~r.inv0)&l.inv0));
re.inv1=((r.inv1&l.inv1)|((~r.inv1)&l.inv0));
return re;
}
void build(int l,int r,int num){
if(l==r){
a[num].f0=a[num].inv0=calc(0,back[l]);
a[num].f1=a[num].inv1=calc(LIM,back[l]);
return;
}
int mid=(l+r)>>1;
build(l,mid,num<<1);build(mid+1,r,(num<<1)+1);
a[num]=update(a[num<<1],a[(num<<1)+1]);
}
void change(int l,int r,int num,int pos){
if(l==r){
a[num].f0=a[num].inv0=calc(0,back[l]);
a[num].f1=a[num].inv1=calc(LIM,back[l]);
return;
}
int mid=(l+r)>>1;
if(mid>=pos) change(l,mid,num<<1,pos);
else change(mid+1,r,(num<<1)+1,pos);
a[num]=update(a[num<<1],a[(num<<1)+1]);
}
data query(int l,int r,int ql,int qr,int num){
if(l==ql&&r==qr) return a[num];
int mid=(l+r)>>1;data re;
if(mid>=qr) re=query(l,mid,ql,qr,num<<1);
else{
if(mid<ql) re=query(mid+1,r,ql,qr,(num<<1)+1);
else re=update(query(l,mid,ql,mid,num<<1),query(mid+1,r,mid+1,qr,(num<<1)+1));//询问时也是同样的合并
}
return re;
}
data ans1[100010],ans2[100010];int cnt1,cnt2;
data solve(int x,int y){
cnt1=cnt2=0;
while(top[x]!=top[y]){
if(dep[top[x]]>=dep[top[y]]){
ans1[++cnt1]=query(1,n,pos[top[x]],pos[x],1);
x=fa[top[x]];
}
else{
ans2[++cnt2]=query(1,n,pos[top[y]],pos[y],1);
y=fa[top[y]];
}
}
if(dep[x]>dep[y]) ans1[++cnt1]=query(1,n,pos[y],pos[x],1);
else ans2[++cnt2]=query(1,n,pos[x],pos[y],1);
data re;int i;
for(i=1;i<=cnt1;i++) swap(ans1[i].f0,ans1[i].inv0),swap(ans1[i].f1,ans1[i].inv1);//反过来
if(cnt1){
re=ans1[1];
for(i=2;i<=cnt1;i++) re=update(re,ans1[i]);
if(cnt2) re=update(re,ans2[cnt2]);
}
else re=ans2[cnt2];
for(i=cnt2-1;i>=1;i--) re=update(re,ans2[i]);
return re;
}
int main(){
memset(first,-1,sizeof(first));
n=read();Q=read();K=read();int i,t1,t2,opt;ll t3,ans,tmp0,tmp1;
for(i=1;i<=n;i++) op[i]=read(),val[i]=read();
for(i=1;i<n;i++){
t1=read();t2=read();
add(t1,t2);
}
dfs1(1,0);dfs2(1,1);
build(1,n,1);
while(Q--){
opt=read();t1=read();t2=read();t3=read();
if(opt==2){
op[t1]=t2;val[t1]=t3;
change(1,n,1,pos[t1]);
}
else{
data re=solve(t1,t2);ans=0;
for(i=63;i>=0;i--){//贪心
tmp0=(re.f0>>i)&1ull;
tmp1=(re.f1>>i)&1ull;
if(tmp0>=tmp1||(1ull<<i)>t3) ans|=(tmp0?(1ull<<i):0);
else ans|=(tmp1?(1ull<<i):0),t3-=(1ull<<i);
}
printf("%llu\n",ans);
}
}
}

[YNOI2017][bzoj4811][luogu3613] 由乃的OJ/睡觉困难综合症 [压位+树链剖分+线段树]的更多相关文章

  1. [BZOJ4811][YNOI2017]由乃的OJ(树链剖分+线段树)

    起床困难综合症那题,只要从高往低贪心,每次暴力跑一边看这一位输入0和1分别得到什么结果即可. 放到序列上且带修改,只要对每位维护一个线段树,每个节点分别记录0和1从左往右和从右往左走完这段区间后变成的 ...

  2. 【BZOJ4811】[Ynoi2017]由乃的OJ 树链剖分+线段树

    [BZOJ4811][Ynoi2017]由乃的OJ Description 由乃正在做她的OJ.现在她在处理OJ上的用户排名问题.OJ上注册了n个用户,编号为1-",一开始他们按照编号排名. ...

  3. BZOJ4811 Ynoi2017由乃的OJ(树链剖分+线段树)

    先考虑NOI2014的水题,显然从高位到低位贪心,算一下该位取0和1分别得到什么即可. 加强这个水题,变成询问区间.那么线段树维护该位取0和1从左到右和从右到左走完这个节点表示的区间会变成什么即可,也 ...

  4. 【bzoj4811】[Ynoi2017]由乃的OJ 树链剖分+线段树区间合并

    题解: 好像和noi那题并没有什么区别 只是加上了修改和变成树上 比较显然我们可以用树链剖分来维护

  5. BZOJ4811 [Ynoi2017]由乃的OJ 树链剖分

    原文链接http://www.cnblogs.com/zhouzhendong/p/8085286.html 题目传送门 - BZOJ4811 题意概括 是BZOJ3668长在树上并加上修改和区间询问 ...

  6. Luogu3613 睡觉困难综合征/BZOJ4811 Ynoi2017 由乃的OJ 树链剖分、贪心

    传送门 题意:给出一个$N$个点的树,树上每个点有一个位运算符号和一个数值.需要支持以下操作:修改一个点的位运算符号和数值,或者给出两个点$x,y$并给出一个上界$a$,可以选取一个$[0,a]$内的 ...

  7. 【bzoj4811】[Ynoi2017]由乃的OJ 树链剖分/LCT+贪心

    Description 给你一个有n个点的树,每个点的包括一个位运算opt和一个权值x,位运算有&,l,^三种,分别用1,2,3表示. 每次询问包含三个数x,y,z,初始选定一个数v.然后v依 ...

  8. bzoj4811 [Ynoi2017]由乃的OJ 树链剖分+位运算

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=4811 因为位运算的结果有可合并性,所以可以树链剖分,线段树维护: 细节很多,特别要注意从左往 ...

  9. bzoj4811 [Ynoi2017]由乃的OJ 树链剖分+贪心+二进制

    题目传送门 https://lydsy.com/JudgeOnline/problem.php?id=4811 题解 我现在为什么都写一题,调一天啊,马上真的退役不花一分钱了. 考虑这道题的弱化版 N ...

随机推荐

  1. jquery 标签中的属性操作

    .arrt() 获取匹配的元素集合中的第一个元素的属性值,或设置每一个元素中的一个或多个属性值. .attr(attributeName) $("em").attr("t ...

  2. BZOJ1901: Zju2112 Dynamic Rankings(整体二分 树状数组)

    Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 9094  Solved: 3808[Submit][Status][Discuss] Descript ...

  3. 【ACM之行】◇第一站◇ 2018HDU多校赛总结

    ◇第一站◇ 2018HDU多校赛 十场多校赛下来,也算是给一个初中生开了眼界……看着清华一次次AK(默默立下flag),看着自己被同校的高中生完虐,一个蒟蒻只能给dalao们垫脚

  4. python导包语句执行

    今天在做项目中遇到一个问题,在first_page中引用login的登录方法,第一次执行登录可以正常登录,登录成功后,再选择返回主菜单,回到上个页面,再选择登录时报错“login_class isno ...

  5. JavaScript数组常用的方法

    改变原数组: ※ push,pop,shif,unshift,sort,reverse ※ splice 不改变原数组: ※ concat,join→split,toString,slice push ...

  6. openwrt(二) 配置openwrt及编译

    导航 1. 配置openwrt 2. 编译openwrt 3. 错误记录 1. 配置openwrt 在openwrt的根目录下,执行make menuconfig. 这个界面我也只是了解了这两个选项而 ...

  7. python基础之布尔运算、集合

    布尔值 True 真 False 假 所有的数据类型都自带布尔值,数据只有在0,None和空的时候为False. print(bool()) print(bool()) print(bool('')) ...

  8. TouTiao开源项目 分析笔记16 新闻评论

    1.要达到的效果 1.1.主要效果图 点击了标题栏的消息图标后,然后会跳转到评论详情的页面. 1.2.触发的点击事件 在新闻详情的片段中的菜单点击事件中 设置上方标题栏的消息标的监听事件 case R ...

  9. 从库函数操作RCC的流程来理解偏移变量

    下面是库函数操作RCC流程,看完后有我的疑问:偏移地址的理解 1,库函数直接操作:RCC库函数操作  RCC_APB2PeriphClockCmd ()RCC->APB2ENR |= RCC_A ...

  10. 4525: [Cerc2012]Kingdoms

    4525: [Cerc2012]Kingdoms 题意 n个国家,两两之间可能存在欠债或者被欠债的关系,一个国家破产:其支出大于收入.问一个国家能否坚持到最后. 思路 很有意思的一道题. dp[s]表 ...