在优秀的学长 C202044zxy 讲解下把这道题搞懂了。希望自己也有机会成为学长。
原来线段树还可以这样干!考虑线段树上每个节点用一个数组维护 2 i 2^i 2i的出现次数,也就是二进制下某一位为 1 1 1的数目。这样只看操作一,每个点最多要修改 log V \log V logV次,但是线段树上合并左右儿子也有一个 log V \log V logV的复杂度,复杂度算上去好像是 O ( n log n log 2 V ) O(n\log n\log^2 V) O(nlognlog2V)。
然后有一个很厉害的 trick \text{trick} trick。考虑之前我们每个节点维护的是权值为 2 i 2^i 2i的数出现的次数,现在我们改成维护出现次数为 2 i 2^i 2i的权值的集合,总之我们构造了一个 log n × log V \log n\times \log V logn×logV的 01 01 01矩阵,把每行拿来维护,发现合并的时候就按行从下往上合并就好了。这个时候复杂度发生了微妙的变化:
1.1
1.1
1.1 每次合并的复杂度从
log
V
\log V
logV变成了
log
n
\log n
logn。
1.2
1.2
1.2 设这个区间长度为
l
e
n
len
len,那么只有
2
i
≤
l
e
n
2^i\le len
2i≤len的行才会有值。
现在我们用另一种方法再来算一次复杂度。我们分成普通操作和特殊操作,其中普通操作包括将每个修改区间分成若干个完整的线段树上的区间,并向上合并,这部分复杂度 O ( n log 2 n ) O(n\log^2n) O(nlog2n);特殊操作包括对每个完整的区间进行修改(区间所有数除以 v v v),注意到每个点最多被修改 log V \log V logV次就会全部变成 0 0 0,并且其合并复杂度应该是 log l e n \log len loglen,让计算工具帮我们算一下发现 T ( n ) = 2 T ( n 2 ) + O ( log n ) = O ( n ) T(n)=2T(\frac{n}{2})+O(\log n)=O(n) T(n)=2T(2n)+O(logn)=O(n),这样总复杂度 O ( n log 2 n + n log V ) O(n\log ^2n+n\log V) O(nlog2n+nlogV)就对了。当然我们只是分析了操作一的复杂度,其实操作二的复杂度也是对的,这个可以自己验证。
写了一发,发现之前复杂度之所以算错是因为 操作二的复杂度不需要借助势能来分析,因此从某方面来说操作一和操作二的复杂度计算(或者说操作次数)是独立的。kid_magic 早已意识到了这点,或许这就是神的直觉吧。
这题怎么卡常啊。
用内存池来实现,这样可以把空间优化到 O ( n ) O(n) O(n)。这一点确实比较难想到。
#include<bits/stdc++.h>
#define uint __uint128_t
using namespace std;
const int N=3e5+5;
inline uint read()
{
static char buf[100];uint res=0;
scanf("%s",buf);
for(int i=0;buf[i];++i)
res=res<<4|(buf[i]<='9'?buf[i]-'0':buf[i]-'a'+10);
return res;
}
inline void output(uint res){
if(res>=16)output(res/16);
putchar(res%16>=10?'a'+res%16-10:'0'+res%16);
}
int n,Q;
uint a[N],nums[N<<3],*id=nums;
struct node{
uint *nums;
uint sum;
uint lazytags;
int tag;
uint o;
}t[N<<2];
//下传标记(按位与)
inline void change(const int p,const int l,const int r,const uint val){
if((t[p].o&val)==t[p].o)return;t[p].o&=val;
if(!t[p].tag)t[p].tag=1,t[p].lazytags=val;
else t[p].lazytags&=val;
int len=r-l+1;t[p].sum=0;
for(int i=0;(1<<i)<=len;i++){
t[p].nums[i]&=val;
t[p].sum+=(1<<i)*t[p].nums[i];
}
}
inline void pushdown(const int p,const int l,const int r){
int mid=l+r>>1;
if(t[p].tag){
change(p<<1,l,mid,t[p].lazytags);
change(p<<1|1,mid+1,r,t[p].lazytags);
t[p].tag=0;
}
}
inline void pushup(const int p,const int l,const int r){
int len=r-l+1;uint tmp=0;
for(int i=0;(1<<i)<=len;i++){
t[p].nums[i]=t[p<<1].nums[i]^t[p<<1|1].nums[i]^tmp;
tmp=(tmp&t[p<<1].nums[i])|(tmp&t[p<<1|1].nums[i])|(t[p<<1].nums[i]&t[p<<1|1].nums[i]);
}
t[p].sum=t[p<<1].sum+t[p<<1|1].sum;
t[p].o=t[p<<1].o|t[p<<1|1].o;
}
inline void build(const int p,const int l,const int r){
int len=r-l+1;
t[p].nums=id;for(int i=0;(1<<i)<=len;i++)id++;id++;
if(l==r){
t[p].nums[0]=a[l],t[p].sum=a[l],t[p].o=a[l];
return;
}
int mid=l+r>>1;
build(p<<1,l,mid),build(p<<1|1,mid+1,r);
pushup(p,l,r);
}
inline void modify(const int p,const int l,const int r,const int ql,const int qr,const uint val){
if(!t[p].o||val==1)return;
if(l==r){
t[p].sum/=val,t[p].nums[0]=t[p].sum,t[p].o=t[p].sum;
return;
}
int mid=l+r>>1;pushdown(p,l,r);
if(ql<=mid)modify(p<<1,l,mid,ql,qr,val);
if(mid<qr)modify(p<<1|1,mid+1,r,ql,qr,val);
pushup(p,l,r);
}
inline void update(const int p,const int l,const int r,const int ql,const int qr,const uint val){
if((t[p].o&val)==t[p].o)return;
if(ql<=l&&r<=qr){
change(p,l,r,val);
return;
}
int mid=l+r>>1;pushdown(p,l,r);
if(ql<=mid)update(p<<1,l,mid,ql,qr,val);
if(mid<qr)update(p<<1|1,mid+1,r,ql,qr,val);
pushup(p,l,r);
}
inline uint query(const int p,const int l,const int r,const int ql,const int qr){
if(ql<=l&&r<=qr)return t[p].sum;
int mid=l+r>>1;pushdown(p,l,r);
if(qr<=mid)return query(p<<1,l,mid,ql,qr);
if(mid<ql)return query(p<<1|1,mid+1,r,ql,qr);
return query(p<<1,l,mid,ql,qr)+query(p<<1|1,mid+1,r,ql,qr);
}
int main() {
scanf("%d%d",&n,&Q);
for(int i=1;i<=n;i++)a[i]=read();
build(1,1,n);
for(int i=1;i<=Q;i++){
int op,l,r;uint v;
scanf("%d%d%d",&op,&l,&r);
if(op==1){
v=read();
modify(1,1,n,l,r,v);
}
else if(op==2){
v=read();
update(1,1,n,l,r,v);
}
else{
output(query(1,1,n,l,r)),puts("");
}
}
}