LOJ6100
题目描述
题解
该题大致思路便是:
第一步 求出每一个点最远能保持单调不下降到哪个点(记为
n
x
t
[
i
]
nxt[i]
nxt[i])
第二步 主席树区间修改
b
u
i
l
d
(
r
t
[
i
−
1
]
,
r
t
[
i
]
,
1
,
n
,
i
,
n
x
t
[
i
]
)
build(rt[i-1],rt[i],1,n,i,nxt[i])
build(rt[i−1],rt[i],1,n,i,nxt[i]),区间查询
b
u
i
l
d
(
r
t
[
l
−
1
]
,
r
t
[
r
]
,
1
,
n
,
l
,
r
]
)
build(rt[l-1],rt[r],1,n,l,r])
build(rt[l−1],rt[r],1,n,l,r])
第二步其实比较好实现,主要是第一步。
首先我考虑的是直接二分,但显然是不满足二分单调性的,那么我们尝试做下列转化
我们用
f
[
0
]
[
i
]
[
j
]
f[0][i][j]
f[0][i][j]表示前
i
i
i个数连续异或的过程中,以第
j
j
j位为最高位时,从
0
0
0变成
1
1
1的次数。
f
[
1
]
[
i
]
[
j
]
f[1][i][j]
f[1][i][j]同理,求的方法参考下方代码(能进一步对二进制操作的熟悉)
然后再考虑二分求出对位置
x
x
x的
n
x
t
[
x
]
nxt[x]
nxt[x],假设当前二分值为
y
y
y
如果
s
u
m
[
x
−
1
]
sum[x-1]
sum[x−1](
s
u
m
[
i
]
sum[i]
sum[i]表示前缀异或和)当前枚举的最高位第
j
j
j位为
1
1
1,而且
f
[
0
]
[
y
]
[
j
]
−
f
[
0
]
[
x
−
1
]
[
j
]
>
0
f[0][y][j]-f[0][x-1][j]>0
f[0][y][j]−f[0][x−1][j]>0 那么说明在
x
x
x到
y
y
y之间,发生过
0
−
>
1
−
>
0
0->1->0
0−>1−>0的过程即使得异或值减小,故当前位置不合法,当
s
u
m
[
x
−
1
]
sum[x-1]
sum[x−1]当前枚举的最高位第j位为0时同理,显然这是满足二分单调性的。
之所以得出以上方法,我们有以下依据:
1,一个数对前面异或和的影响只和该数的最高位与前面异或值的当前位的关系有关。
因为一个数的最高位一定为
1
1
1,那么如果前面那个数的当前位也是
1
1
1,那么异或上这个数的话,前面的异或和一定会减小,反之会增大
代码
#include<bits/stdc++.h>
#define int long long
#define M 200009
using namespace std;
int read(){
int f=1,re=0;
char ch;
for(ch=getchar();!isdigit(ch)&&ch!='-';ch=getchar());
if(ch=='-'){f=-1,ch=getchar();}
for(;isdigit(ch);ch=getchar()) re=(re<<3)+(re<<1)+ch-'0';
return re*f;
}
struct tree{
int l,r,sum,add;
}tr[M*32];
int n,q,f[2][M][36],sum[M],cnt,rt[M],lastans;
bool bit(int x,int i){return (x>>i)&1;}
int gethigh(int x){
for(int i=33;i>=0;i--)
if(bit(x,i)) return i;
return -1;
}
bool check(int x,int y){
for(int i=33;i>=0;i--){
if(bit(sum[x-1],i)&&f[0][y][i]-f[0][x-1][i]>0) return false;
if(!bit(sum[x-1],i)&&f[1][y][i]-f[1][x-1][i]>0) return false;
}return true;
}
int getnxt(int pos){
int l=pos,r=n;
while(l<r){
int mid=(l+r+1)>>1;
if(check(pos,mid)) l=mid;
else r=mid-1;
}return l;
}
void build(int x,int &y,int l,int r,int ql,int qr){
y=++cnt,tr[y]=tr[x];
if(l==ql&&r==qr){
tr[y].add++;
return;
}tr[y].sum+=(qr-ql+1);
int mid=(l+r)>>1;
if(qr<=mid) build(tr[x].l,tr[y].l,l,mid,ql,qr);
else if(ql>mid) build(tr[x].r,tr[y].r,mid+1,r,ql,qr);
else build(tr[x].l,tr[y].l,l,mid,ql,mid),build(tr[x].r,tr[y].r,mid+1,r,mid+1,qr);
}
int query(int x,int y,int l,int r,int ql,int qr){
int tmp=(tr[y].add-tr[x].add)*(qr-ql+1);
if(l==ql&&r==qr) return tmp+tr[y].sum-tr[x].sum;
int mid=(l+r)>>1;
if(qr<=mid) return tmp+query(tr[x].l,tr[y].l,l,mid,ql,qr);
else if(ql>mid) return tmp+query(tr[x].r,tr[y].r,mid+1,r,ql,qr);
else return tmp+query(tr[x].l,tr[y].l,l,mid,ql,mid)+query(tr[x].r,tr[y].r,mid+1,r,mid+1,qr);
}
signed main(){
n=read();
for(int i=1;i<=n;i++){
int x=read();
sum[i]=sum[i-1]^x;
for(int j=0;j<=33;j++) f[0][i][j]=f[0][i-1][j],f[1][i][j]=f[1][i-1][j];
int h=gethigh(x);
if(h!=-1){
if(bit(sum[i-1],h)) f[1][i][h]++;
else f[0][i][h]++;
}
}for(int i=1;i<=n;i++) build(rt[i-1],rt[i],1,n,i,getnxt(i));
q=read();
for(int i=1;i<=q;i++){
int l=(read()+lastans)%n+1,r=(read()+lastans)%n+1;
if(l>r) swap(l,r);
printf("%lld\n",lastans=query(rt[l-1],rt[r],1,n,l,r));
lastans%=n;
}return 0;
}
启发
1,对二进制的用法有了一定的理解
2,对主席树的可持久化有了更深的理解,主席树更像是记录下每一个操作,因此这里操作可以包括区间操作(但貌似只能用lazy tag)和单点操作,查询也是同样的