题目来源
Description
- 给定一个长度为 n n n 的数列 { a n } \{a_n\} {an},给定 q q q 次操作,有如下 3 3 3 种操作:
A l r x
,把区间 [ l , r ] [l,r] [l,r] 中的数分别加上 x x x。P l r
,对于区间 [ l , r ] [l,r] [l,r] 中的数,令 a i ← p o p c o u n t ( a i ) a_i\leftarrow \mathrm{popcount}(a_i) ai←popcount(ai),其中 p o p c o u n t ( x ) \mathrm{popcount}(x) popcount(x) 表示 x x x 的二进制表示中 1 1 1 的个数。J p
,查询 a p a_p ap 的值。- 1 ≤ n ≤ 3 × 1 0 5 , 1 ≤ q ≤ 1 0 6 , 1 ≤ a i , x ≤ 1 0 9 1\le n\le3\times10^5,1\le q\le10^6,1\le a_i,x\le10^9 1≤n≤3×105,1≤q≤106,1≤ai,x≤109。
Solution
首先由于操作为区间修改+单点查询,很自然的想到运用线段树。
但由于线段树所维护的数据需要具有可结合性,即已知左右两个子节点的数据可以推出父亲节点的数据,所以本题需要某些特殊的操作来达到此目的。
区间加操作显然很好做,但是 p o p c o u n t \mathrm{popcount} popcount 则不具有结合性,考虑怎么进行结合。
由于在进行若干次区间加和区间 p o p c o u n t \mathrm{popcount} popcount(以下简称 P P P) 操作后,必然可以写作 P ( ⋯ P ( x + b u ) ⋯ ) + c u P(\cdots P(x+b_u)\cdots)+c_u P(⋯P(x+bu)⋯)+cu 的形式(其中 x x x 为从子节点传上来的值,无法确定)。
那么我们可以令线段树上每个节点存储 b u , c u , f u , F u b_u,c_u,f_u,F_u bu,cu,fu,Fu,其中 b u b_u bu 表示该区间第一次 P P P 操作之前所加上的值的总和, c u c_u cu 表示该区间最后一次 P P P 操作后所加上的值的总和,而 f f f 为一个 64 64 64 位的数组,表示一组映射关系(初始设 f u [ i ] = i f_u[i]=i fu[i]=i),即 f P ( x + b u ) = P ( ⋯ P ( x + b u ) ⋯ ) + c u f_{P(x+b_u)}=P(\cdots P(x+b_u)\cdots)+c_u fP(x+bu)=P(⋯P(x+bu)⋯)+cu。
而由于我们无法确定 P ( x + b u ) P(x+b_u) P(x+bu) 的值,而其值域仅为 [ 0 , 64 ) [0,64) [0,64),那么我们每次操作就需要将所有 f i ∈ [ 0 , 64 ) f_{i\in[0,64)} fi∈[0,64) 的值求出来。
而 F u F_u Fu 为 0 / 1 0/1 0/1 表示该区间是否已经出现过 P P P 操作。
由于仅有单点查询,则无需考虑 p u s h u p \mathrm{pushup} pushup 操作,仅需考虑 p u s h d o w n \mathrm{pushdown} pushdown。
设父亲节点为 u u u ,需要下传到的子节点为 v v v。则对于 v v v 的所有操作均位于 u u u 的操作之前,因为若 u u u 存在早于 v v v 的操作,必然会在之前的过程中下传。
那么考虑根据 F u , F v F_u,F_v Fu,Fv 的取值分类讨论:
- 若 F u = 0 F_u=0 Fu=0 且 F v = 0 F_v=0 Fv=0,说明均只存在区间加操作,显然令 b v ← b v + b u b_v\leftarrow b_v+b_u bv←bv+bu 即可。
- 否则,若 F u = 0 F_u=0 Fu=0 且 F v = 1 F_v=1 Fv=1,说明在完成 v v v 的最后一次 P P P 操作后,需要加上 c v c_v cv,并再次加上 b u b_u bu,那么令 c v ← c v + b u c_v\leftarrow c_v+b_u cv←cv+bu 即可。
- 否则,若 F u = 1 F_u=1 Fu=1 且 F v = 0 F_v=0 Fv=0,说明在完成 v v v 的加操作,再加上 b u b_u bu 之后完成 u u u 的一系列操作即可得出结果。而与 u u u 相比, v v v 只改变了 u u u 第一次 P P P 操作前的操作,对 f f f 数组无影响,那么令 f v ← f u , b v ← b v + b u , c v ← c u , F v ← 1 f_v\leftarrow f_u,b_v\leftarrow b_v+b_u,c_v\leftarrow c_u,F_v\leftarrow1 fv←fu,bv←bv+bu,cv←cu,Fv←1 即可
- 否则,说明 f v [ P ( x + b v ) ] + c v f_v[P(x+b_v)]+c_v fv[P(x+bv)]+cv 代表 u u u 中的 x x x,那么令 f v [ i ] ← f u [ P ( f v [ i ] + c v + b u ) ] , c v ← c u f_v[i]\leftarrow f_u[P(f_v[i]+c_v+b_u)],c_v\leftarrow c_u fv[i]←fu[P(fv[i]+cv+bu)],cv←cu 即可。
更新完后记得将 u u u 节点内存储的所有数据清空。
区间加操作对整个区间进行修改时进行的操作:若 F p = 1 F_p=1 Fp=1,则令 c p ← c p + x c_p\leftarrow c_p+x cp←cp+x;否则令 b p ← b p + x b_p\leftarrow b_p+x bp←bp+x。
区间 P P P 操作对整个区间进行修改时进行的操作:若 F p = 1 F_p=1 Fp=1,则令 f p [ i ] ← P ( f p [ i ] + c p ) , c p ← 0 f_p[i]\leftarrow P(f_p[i]+c_p),c_p\leftarrow0 fp[i]←P(fp[i]+cp),cp←0;否则令 F p ← 1 F_p\leftarrow1 Fp←1。
单点查询访问到线段树上编号为 p p p 的叶子节点 [ l , l ] [l,l] [l,l] 即返回 f p [ P ( a l ) + b p ] + c p f_p[P(a_l)+b_p]+c_p fp[P(al)+bp]+cp 。
其他操作按照线段树基本方式完成即可。
Code
#include <bits/stdc++.h>
using namespace std;
int n,q,a[300005];
struct Node{
int l,r;
long long b,c;
short f[70]; // 初始时 f[i]=i(不开short可能爆空间)
bool F;
// 存储形如P(...P(x+b)...)+c的信息(x表示从子节点上来的值),b为第一次P前加操作的总和
// F若为0说明还没有P操作,否则f[P(x+b)]=P(...P(x+b)...)+c,c为最后一次P后加操作的总和
}tree[1200005];
void pushdown(int u,int v){
if (!tree[u].F&&!tree[v].F) tree[v].b+=tree[u].b;
else if (!tree[u].F&&tree[v].F) tree[v].c+=tree[u].b;
else if (tree[u].F&&!tree[v].F){
for (int i=0;i<=64;i++) tree[v].f[i]=tree[u].f[i];
tree[v].b+=tree[u].b;
tree[v].c=tree[u].c;
tree[v].F=1;
}
else{
for (int i=0;i<=64;i++) tree[v].f[i]=tree[u].f[__builtin_popcountll(tree[v].f[i]+tree[v].c+tree[u].b)];
tree[v].c=tree[u].c;
}
}
void clear(int p){
tree[p].b=tree[p].c=tree[p].F=0;
for (int i=0;i<=64;i++) tree[p].f[i]=short(i);
}
void build(int p,int l,int r){
tree[p].l=l,tree[p].r=r;
clear(p);
if (l==r){
tree[p].b=a[l];
return;
}
int Mid=(l+r)>>1;
build(p<<1,l,Mid);
build(p<<1|1,Mid+1,r);
}
void update_add(int p,int l,int r,int x){
if (l<=tree[p].l&&tree[p].r<=r){
if (tree[p].F) tree[p].c+=x;
else tree[p].b+=x;
return;
}
pushdown(p,p<<1),pushdown(p,p<<1|1),clear(p);
int Mid=(tree[p].l+tree[p].r)>>1;
if (l<=Mid) update_add(p<<1,l,r,x);
if (r>Mid) update_add(p<<1|1,l,r,x);
}
void update_pop(int p,int l,int r){
if (l<=tree[p].l&&tree[p].r<=r){
if (tree[p].F){
for (int i=0;i<=64;i++) tree[p].f[i]=__builtin_popcountll(tree[p].f[i]+tree[p].c);
tree[p].c=0;
}
else tree[p].F=1;
return;
}
pushdown(p,p<<1),pushdown(p,p<<1|1),clear(p);
int Mid=(tree[p].l+tree[p].r)>>1;
if (l<=Mid) update_pop(p<<1,l,r);
if (r>Mid) update_pop(p<<1|1,l,r);
}
long long query(int p,int x){
if (tree[p].l==tree[p].r){
if (tree[p].F) return tree[p].f[__builtin_popcountll(tree[p].b)]+tree[p].c;
else return tree[p].b;
}
pushdown(p,p<<1),pushdown(p,p<<1|1),clear(p);
int Mid=(tree[p].l+tree[p].r)>>1;
if (x<=Mid) return query(p<<1,x);
return query(p<<1|1,x);
}
int main(){
scanf("%d%d",&n,&q);
for (int i=1;i<=n;i++) scanf("%d",&a[i]);
build(1,1,n);
for (int i=1,l,r,x;i<=q;i++){
char op;
scanf(" %c",&op);
if (op=='A'){
scanf("%d%d%d",&l,&r,&x);
update_add(1,l,r,x);
}
if (op=='P'){
scanf("%d%d",&l,&r);
update_pop(1,l,r);
}
if (op=='J'){
scanf("%d",&x);
printf("%lld\n",query(1,x));
}
}
return 0;
}