BZOJ4811: [YNOI2017] 由乃的OJ
题目大意
给定一棵$N$个点的树$(N\leq 100000)$,每个节点有一个点权和一种位运算符号(&(与)、|(或)、^(异或)三种之一),支持两种操作:
1、给定一个点$u$,一个数值$m$,一个符号$k$(1表示&,2表示|,3表示^),将点$u$的点权修改为$m$,符号修改成$k$。
2、给定一条路径的两个端点(有可能是同一个点)$u、v$,再给定一个数值$k$,查询对于$[0,k]$中的每一个整数沿着从u到v的路径顺次进行运算后的最大值。
点权$V_i < 2^{64}-1$
题解
树上支持修改点和查询路径信息,很容易让人联想到树链剖分。
对于某一个数,经过这条确定的路径之后结果是确定的,但我们无法一次性解决所有数经过某条路径后的值,于是考虑拆位,对于每一位为$1$或$0$单独求出经过运算的值,这样不仅可以解决在运算过程中对于每一位的重复运算,还可在二进制下贪心地求出$[0,k]$中每个数经过运算的最大值。
即,假设我们已经求出第$0$至$63$每一位为$0、1$时的结果,我们从高位向低位依次考虑,设一开始在$[0,k]$中选出的数为$0$,最终答案也是$0$,若当前位为$0$在运算之后变为$1$,则将最终答案加上这一位的真实值且选出的数不变,否则,若当前位为$1$时在运算之后变为$1$,而且我们目前选出的数加上当前位的值不超过$k$,就把最终答案和选出的数都加上当前位的值。再要不然就只能舍弃这一位,即这一位对最终答案无贡献。
现在,问题只剩下如何用线段树维护某条路径的答案了,如果我们对每一位单独维护一颗线段树,那么复杂度位$O(64\times m\cdot log^2(n))$显然会$TLE$,由于降低树剖本身的复杂度几乎不可能,于是我们考虑优化那个$64$的常数。于是我们考虑再压位,因为$64$位$0$和$64$位$1$总共有$128$个结果,而每一个结果也是$0和1$,所以对于每一个线段树节点存$2$个$unsigned$ $long$ $long$,利用位运算的优秀复杂度来加速两个线段树节点的合并,最终复杂度为$O(64\times m+m\cdot log^2(n))$,注意路径有向所以要考虑两个方向。
AC代码如下:
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#define ULL unsigned long long
#define mid (l+r>>1)
#define M 200010
using namespace std;
const ULL u1=1;
ULL read(){
ULL nm=0,fh=1;char cw=getchar();
for(;!isdigit(cw);cw=getchar()) ;
for(;isdigit(cw);cw=getchar()) nm=nm*10+(cw-'0');
return nm*fh;
}
ULL n,m,K,tp[M],fa[M],nt[M<<1],fs[M],to[M<<1],kd[M],val[M],MAX;
ULL dfn[M],sz[M],mxs[M],u,v,dep[M],rt,tk[M],cnt,tpe,T,tmp=1;
ULL calc(ULL num,ULL bas,ULL op){
if(op==1) return num&bas;
if(op==2) return num|bas;
return num^bas;
}
void dfs1(ULL x){
sz[x]=u1,mxs[x]=0;
ULL maxn=0;
for(ULL i=fs[x];i>0;i=nt[i]){
if(to[i]==fa[x]) continue;
fa[to[i]]=x,dep[to[i]]=dep[x]+u1;
dfs1(to[i]),sz[x]+=sz[to[i]];
if(sz[to[i]]>maxn) maxn=sz[to[i]],mxs[x]=to[i];
}
}
void dfs2(ULL x,ULL top){
dfn[x]=++cnt,tp[x]=top,tk[cnt]=x;
if(mxs[x]>0) dfs2(mxs[x],top);
for(ULL i=fs[x];i>0;i=nt[i]){
if(to[i]==mxs[x]||to[i]==fa[x]) continue;
dfs2(to[i],to[i]);
}
}
struct Seg{
ULL d[2][2];
void init(ULL vl,ULL op){d[0][0]=d[1][0]=calc(0,vl,op),d[0][1]=d[1][1]=calc(MAX,vl,op);}
void turn(){swap(d[0][0],d[1][0]),swap(d[0][1],d[1][1]);}
void merge(Seg L,Seg R){
d[0][0]=(L.d[0][0]&R.d[0][1])|((~L.d[0][0])&R.d[0][0]);
d[1][0]=(R.d[1][0]&L.d[1][1])|((~R.d[1][0])&L.d[1][0]);
d[0][1]=(L.d[0][1]&R.d[0][1])|((~L.d[0][1])&R.d[0][0]);
d[1][1]=(R.d[1][1]&L.d[1][1])|((~R.d[1][1])&L.d[1][0]);
}
}p[M<<2],ans;
void build(ULL x,ULL l,ULL r){
if(l==r) return p[x].init(val[tk[l]],kd[tk[l]]);
else build(x<<1,l,mid),build(x<<1|1,mid+1,r),p[x].merge(p[x<<1],p[x<<1|1]);
}
void change(ULL x,ULL l,ULL r,ULL pos,ULL op,ULL num){
if(l==r) return p[x].init(num,op);
if(pos<=mid) change(x<<1,l,mid,pos,op,num);
else change(x<<1|1,mid+1,r,pos,op,num);
p[x].merge(p[x<<1],p[x<<1|1]);
}
Seg query(ULL x,ULL l,ULL r,ULL L,ULL R){
if(L<=l&&r<=R) return p[x];
Seg sum; sum.init(0,3);
if(r<L||R<l) return sum;
sum.merge(query(x<<1,l,mid,L,R),query(x<<1|1,mid+1,r,L,R));
return sum;
}
ULL getans(ULL x,ULL y,ULL num){
Seg res1,res2,cur; ULL tot=0;
res1.init(0,3),res2.init(0,3);
while(tp[x]!=tp[y]){
if(dep[tp[x]]>dep[tp[y]]) res1.merge(query(1,1,n,dfn[tp[x]],dfn[x]),res1),x=fa[tp[x]];
else res2.merge(query(1,1,n,dfn[tp[y]],dfn[y]),res2),y=fa[tp[y]];
}
if(dep[x]>dep[y]) res1.merge(query(1,1,n,dfn[y],dfn[x]),res1);
else res2.merge(query(1,1,n,dfn[x],dfn[y]),res2);
res1.turn(),cur.merge(res1,res2);
for(ULL i=K;i-->0;){
if(cur.d[0][0]&(u1<<i)) tot+=(u1<<i);
else if((cur.d[0][1]&(u1<<i))>0&&num>=(u1<<i)) num-=(u1<<i),tot+=(u1<<i);
}
return tot;
}
void link(ULL x,ULL y){nt[tmp]=fs[x],fs[x]=tmp,to[tmp++]=y;}
int main(){
n=read(),T=read(),K=read(),rt=(n+1>>1);
for(ULL i=0;i<K;i++) MAX+=(((ULL)1)<<i);
for(ULL i=1;i<=n;i++) kd[i]=read(),val[i]=read(),fs[i]=0;
for(ULL i=1;i<n;i++) u=read(),v=read(),link(u,v),link(v,u);
dfs1(rt),dfs2(rt,rt),build(1,1,n);
while(T--){
tpe=read(),u=read(),v=read(),m=read();
if(tpe==2) change(1,1,n,dfn[u],v,m);
else printf("%llu\n",getans(u,v,m));
}
return 0;
}