吉老师线段树
吉如一线段树
\([BZOJ4695 最假女选手]\)
题意:
在刚刚结束的水题嘉年华的压轴节目放水大赛中,\(wyy\)如愿以偿的得到了最假女选手的奖项。但是作为主办人的 C_SUNSHINE为了证明
\(wyy\)确实在放水,决定出一道基础题考察\(wyy\)的姿势水平。给定一个长度为 \(N\)序列,编号 从\(1\) 到$ N$。要求支持下面几种操作
1.给一个区间\([L,R]\) 加上一个数\(x\)
2.把一个区间\([L,R]\) 里小于\(x\) 的数变成\(x\),即\(a_i=max(a_i,x)\) \(l\le i\le r\)
3.把一个区间\([L,R]\) 里大于x 的数变成x,即\(a_i=min(a_i,x)\) \(l\le i\le r\)
4.求区间\([L,R]\) 的和
5.求区间\([L,R]\) 的最大值
6.求区间\([L,R]\) 的最小值
Sol
难点在于操作2和3。以操作2为例,我们记录区间的最大值\(mx\)、最小值\(mn\)和次大值\(sx\)、次小值\(sn\),还有最大值的数量\(cx\),最小值的数量\(cn\)。更新区间时,如果当前的\(x\)的小于最小值,显然当前的区间不用更新,如果大于最小值但小于次小值,暴力更新最小值的值对答案的贡献,这样操作根据吉老师的论文时间复杂度最坏是\(O(mlog^2n\))
//此写法常数巨大
#include <bits/stdc++.h>
#define ls rt<<1
#define rs rt<<1|1
#define ll long long
using namespace std;
constexpr int N=5e5+10,inf=2000000000;
struct Segment_Tree
{
int l,r,tag;
ll sum;
int mx,sx,cx; //区间最大值,次大值,最大值出现次数
int mn,sn,cn;
}tr[N*4];
int a[N];
void pushup(int rt)
{
tr[rt].sum=tr[ls].sum+tr[rs].sum;
if(tr[ls].mx>tr[rs].mx) tr[rt].mx=tr[ls].mx,tr[rt].cx=tr[ls].cx,tr[rt].sx=max(tr[ls].sx,tr[rs].mx);
if(tr[ls].mx<tr[rs].mx) tr[rt].mx=tr[rs].mx,tr[rt].cx=tr[rs].cx,tr[rt].sx=max(tr[rs].sx,tr[ls].mx);
if(tr[ls].mx==tr[rs].mx) tr[rt].mx=tr[ls].mx,tr[rt].cx=tr[ls].cx+tr[rs].cx,tr[rt].sx=max(tr[ls].sx,tr[rs].sx);
if(tr[ls].mn<tr[rs].mn) tr[rt].mn=tr[ls].mn,tr[rt].cn=tr[ls].cn,tr[rt].sn=min(tr[ls].sn,tr[rs].mn);
if(tr[ls].mn>tr[rs].mn) tr[rt].mn=tr[rs].mn,tr[rt].cn=tr[rs].cn,tr[rt].sn=min(tr[rs].sn,tr[ls].mn);
if(tr[ls].mn==tr[rs].mn) tr[rt].mn=tr[ls].mn,tr[rt].cn=tr[ls].cn+tr[rs].cn,tr[rt].sn=min(tr[ls].sn,tr[rs].sn);
}
void build(int rt,int l,int r)
{
tr[rt].l=l,tr[rt].r=r;
if(l==r)
{
tr[rt].sum=tr[rt].mx=tr[rt].mn=a[l];
tr[rt].cx=tr[rt].cn=1;
tr[rt].sx=-inf,tr[rt].sn=inf;
return;
}
int mid=l+r>>1;
build(ls,l,mid);
build(rs,mid+1,r);
pushup(rt);
}
void pushdown(int rt)
{
if(tr[rt].tag)
{
int x=tr[rt].tag;
tr[rt].tag=0;
tr[ls].mx+=x,tr[ls].sx+=x,tr[ls].mn+=x,tr[ls].sn+=x,tr[ls].tag+=x,tr[ls].sum+=(ll)x*(tr[ls].r-tr[ls].l+1);
tr[rs].mx+=x,tr[rs].sx+=x,tr[rs].mn+=x,tr[rs].sn+=x,tr[rs].tag+=x,tr[rs].sum+=(ll)x*(tr[rs].r-tr[rs].l+1);
}
if(tr[rt].mx<tr[ls].mx)
{
if(tr[ls].mn==tr[ls].mx) tr[ls].mn=tr[rt].mx;
if(tr[ls].sn==tr[ls].mx) tr[ls].sn=tr[rt].mx;
tr[ls].sum-=1ll*(tr[ls].mx-tr[rt].mx)*tr[ls].cx;
tr[ls].mx=tr[rt].mx;
}
if(tr[rt].mx<tr[rs].mx)
{
if(tr[rs].mn==tr[rs].mx) tr[rs].mn=tr[rt].mx;
if(tr[rs].sn==tr[rs].mx) tr[rs].sn=tr[rt].mx;
tr[rs].sum-=1ll*(tr[rs].mx-tr[rt].mx)*tr[rs].cx;
tr[rs].mx=tr[rt].mx;
}
if(tr[rt].mn>tr[ls].mn)
{
if(tr[ls].mx==tr[ls].mn) tr[ls].mx=tr[rt].mn;
if(tr[ls].sx==tr[ls].mn) tr[ls].sx=tr[rt].mn;
tr[ls].sum+=1ll*(tr[rt].mn-tr[ls].mn)*tr[ls].cn;
tr[ls].mn=tr[rt].mn;
}
if(tr[rt].mn>tr[rs].mn)
{
if(tr[rs].mx==tr[rs].mn) tr[rs].mx=tr[rt].mn;
if(tr[rs].sx==tr[rs].mn) tr[rs].sx=tr[rt].mn;
tr[rs].sum+=1ll*(tr[rt].mn-tr[rs].mn)*tr[rs].cn;
tr[rs].mn=tr[rt].mn;
}
}
void update_add(int rt,int l,int r,int x)
{
int L=tr[rt].l,R=tr[rt].r;
if(l<=L&&R<=r)
{
tr[rt].sum+=(ll)(R-L+1)*x;
tr[rt].mx+=x,tr[rt].sx+=x;
tr[rt].mn+=x,tr[rt].sn+=x;
tr[rt].tag+=x;
return;
}
pushdown(rt);
int mid=L+R>>1;
if(l<=mid) update_add(ls,l,r,x);
if(r>mid) update_add(rs,l,r,x);
pushup(rt);
}
void update_max(int rt,int l,int r,int x)
{
if(tr[rt].mn>=x) return;//区间最小值都比x大就不需要更新
int L=tr[rt].l,R=tr[rt].r;
if(l<=L&&R<=r&&tr[rt].sn>x)
{
tr[rt].sum+=(ll)(x-tr[rt].mn)*tr[rt].cn;
if(tr[rt].sx==tr[rt].mn) tr[rt].sx=x;
if(tr[rt].mx==tr[rt].mn) tr[rt].mx=x;
tr[rt].mn=x;
return;
}
int mid=L+R>>1;
pushdown(rt);
if(l<=mid) update_max(ls,l,r,x);
if(r>mid) update_max(rs,l,r,x);
pushup(rt);
}
void update_min(int rt,int l,int r,int x)
{
if(tr[rt].mx<=x) return;//区间最大值都比x小就不需要更新
int L=tr[rt].l,R=tr[rt].r;
if(l<=L&&R<=r&&tr[rt].sx<x)
{
tr[rt].sum-=(ll)(tr[rt].mx-x)*tr[rt].cx;
if(tr[rt].sn==tr[rt].mx) tr[rt].sn=x;
if(tr[rt].mn==tr[rt].mx) tr[rt].mn=x;
tr[rt].mx=x;
return;
}
int mid=L+R>>1;
pushdown(rt);
if(l<=mid) update_min(ls,l,r,x);
if(r>mid) update_min(rs,l,r,x);
pushup(rt);
}
ll query_sum(int rt,int l,int r)
{
int L=tr[rt].l,R=tr[rt].r;
ll res=0;
if(l<=L&&R<=r)
{
res+=tr[rt].sum;
return res;
}
pushdown(rt);
int mid=L+R>>1;
if(l<=mid) res+=query_sum(ls,l,r);
if(r>mid) res+=query_sum(rs,l,r);
return res;
}
int query_max(int rt,int l,int r)
{
int L=tr[rt].l,R=tr[rt].r;
int res=-inf;
if(l<=L&&R<=r)
{
res=max(res,tr[rt].mx);
return res;
}
pushdown(rt);
int mid=L+R>>1;
if(l<=mid) res=max(res,query_max(ls,l,r));
if(r>mid) res=max(res,query_max(rs,l,r));
return res;
}
int query_min(int rt,int l,int r)
{
int L=tr[rt].l,R=tr[rt].r;
int res=inf;
if(l<=L&&R<=r)
{
res=min(res,tr[rt].mn);
return res;
}
pushdown(rt);
int mid=L+R>>1;
if(l<=mid) res=min(res,query_min(ls,l,r));
if(r>mid) res=min(res,query_min(rs,l,r));
return res;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n,m;
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
build(1,1,n);
cin>>m;
while(m--)
{
int type,l,r,x;
cin>>type;
if(type==1)
{
cin>>l>>r>>x;
update_add(1,l,r,x);
}
else if(type==2)
{
cin>>l>>r>>x;
update_max(1,l,r,x);
}
else if(type==3)
{
cin>>l>>r>>x;
update_min(1,l,r,x);
}
else if(type==4)
{
cin>>l>>r;
cout<<query_sum(1,l,r)<<'\n';
}else if(type==5)
{
cin>>l>>r;
cout<<query_max(1,l,r)<<'\n';
}else if(type==6)
{
cin>>l>>r;
cout<<query_min(1,l,r)<<'\n';
}
}
return 0;
}
\(J - Just Another Game of Stones\)
题意:
给定\(n\)堆石子,有\(m\)次操作,操作分为\(2\)种
\(1.\) \(l\) \(r\) \(x\) 令\(a_i=max(a_i,x)\) \(i\in[l,r]\)
\(2.\) \(l\) \(r\) \(x\) 用区间\([l,r]\)的石子和大小为\(x\)的一堆石子进行\(Nim\)游戏,询问先手必胜的的第一步操作数。(第一步操作可以在某一堆石子中拿走一点)
Sol:
操作1直接利用吉司机线段树
\(Nim\)游戏:先手必胜异或不为0,先手必败异或为0
设总共的异或值为\(S\),操作2对区间中的一个\(a_i\),注意到第\(i\)堆拿走\(a_i-a_i\oplus S\)就可以使得剩下的异或值一定为\(0\),所以只需要查看区间中有多少\(a_i>a_i\oplus S\),假设\(S\)的最高位\(1\)的位数是\(bit\),进一步只需要查看区间中有多少个数第\(bit\)为是\(1\)。为什么?因为\(a_i\oplus S\)的第\(bit\)位一定是\(0\),而\(a_i\oplus S\)第\(bit\)位前和\(a_i\)一样,而\(a_i\)的第\(bit\)位是\(1\),所以\(a_i>a_i\oplus S\);如果选的第\(bit\)位的数不是\(1\),那么\(a_i\oplus S\)的第\(bit\)位是\(1\),那么\(a_i\oplus S>a_i\),不符。
//此写法常数巨大
#include <bits/stdc++.h>
#define ls rt<<1
#define rs rt<<1|1
#define ll long long
using namespace std;
constexpr int N=2e5+10,inf=1<<30;
struct Segment_Tree
{
int l,r,tag;
int sum;
int mn,sn,cn;
int bit[31];
}tr[N*4];
int a[N];
void pushup(int rt)
{
tr[rt].sum=tr[ls].sum^tr[rs].sum;
for(int i=0;i<=30;i++)
tr[rt].bit[i]=tr[ls].bit[i]+tr[rs].bit[i];
if(tr[ls].mn<tr[rs].mn) tr[rt].mn=tr[ls].mn,tr[rt].cn=tr[ls].cn,tr[rt].sn=min(tr[ls].sn,tr[rs].mn);
if(tr[ls].mn>tr[rs].mn) tr[rt].mn=tr[rs].mn,tr[rt].cn=tr[rs].cn,tr[rt].sn=min(tr[rs].sn,tr[ls].mn);
if(tr[ls].mn==tr[rs].mn) tr[rt].mn=tr[ls].mn,tr[rt].cn=tr[ls].cn+tr[rs].cn,tr[rt].sn=min(tr[ls].sn,tr[rs].sn);
}
void build(int rt,int l,int r)
{
tr[rt].l=l,tr[rt].r=r,tr[rt].tag=-1;
if(l==r)
{
tr[rt].sum=tr[rt].mn=a[l];
tr[rt].cn=1;
tr[rt].sn=inf;
for(int i=0;i<=30;i++)
if((a[l]>>i)&1) tr[rt].bit[i]++;
return;
}
int mid=l+r>>1;
build(ls,l,mid);
build(rs,mid+1,r);
pushup(rt);
}
void pushdown(int rt)
{
if(tr[rt].tag==-1) return;
if(tr[rt].tag>tr[ls].mn)
{
if(tr[ls].cn&1) tr[ls].sum^=tr[ls].mn^tr[rt].tag;
for(int i=0;i<=30;i++)
{
if((tr[ls].mn>>i)&1) tr[ls].bit[i]-=tr[ls].cn;
if((tr[rt].tag>>i)&1) tr[ls].bit[i]+=tr[ls].cn;
}
tr[ls].tag=tr[rt].tag;
tr[ls].mn=tr[rt].tag;
}
if(tr[rt].tag>tr[rs].mn)
{
if(tr[rs].cn&1) tr[rs].sum^=tr[rs].mn^tr[rt].tag;
for(int i=0;i<=30;i++)
{
if((tr[rs].mn>>i)&1) tr[rs].bit[i]-=tr[rs].cn;
if((tr[rt].tag>>i)&1) tr[rs].bit[i]+=tr[rs].cn;
}
tr[rs].tag=tr[rt].tag;
tr[rs].mn=tr[rt].tag;
}
tr[rt].tag=-1;
}
void update_max(int rt,int l,int r,int x)
{
if(tr[rt].mn>=x) return;//区间最小值都比x大就不需要更新
int L=tr[rt].l,R=tr[rt].r;
if(l<=L&&R<=r&&tr[rt].sn>x)
{
if(tr[rt].cn&1)tr[rt].sum^=tr[rt].mn^x;//偶数个数,异或为0
for(int i=0;i<=30;i++)
{
if((tr[rt].mn>>i)&1) tr[rt].bit[i]-=tr[rt].cn;
if((x>>i)&1) tr[rt].bit[i]+=tr[rt].cn;
}
tr[rt].tag=x;
tr[rt].mn=x;
return;
}
int mid=L+R>>1;
pushdown(rt);
if(l<=mid) update_max(ls,l,r,x);
if(r>mid) update_max(rs,l,r,x);
pushup(rt);
}
int query_sum(int rt,int l,int r)
{
int L=tr[rt].l,R=tr[rt].r;
int res=0;
if(l<=L&&R<=r)
return tr[rt].sum;
pushdown(rt);
int mid=L+R>>1;
if(l<=mid) res^=query_sum(ls,l,r);
if(r>mid) res^=query_sum(rs,l,r);
return res;
}
int query_bit(int rt,int l,int r,int Bit)
{
int L=tr[rt].l,R=tr[rt].r;
if(l<=L&&R<=r)
{
return tr[rt].bit[Bit];
}
pushdown(rt);
int mid=L+R>>1;
int res=0;
if(l<=mid) res+=query_bit(ls,l,r,Bit);
if(r>mid) res+=query_bit(rs,l,r,Bit);
return res;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
build(1,1,n);
while(m--)
{
int type,l,r,x;
cin>>type;
cin>>l>>r>>x;
if(type==1)
update_max(1,l,r,x);
else
{
int sum=query_sum(1,l,r);
sum^=x;
int hb=-1;
for(int i=30;i>=0;i--)
if((sum>>i)&1)
{
hb=i;
break;
}
if(hb==-1) cout<<0<<'\n';
else cout<<query_bit(1,l,r,hb)+((x>>hb)&1)<<'\n';
}
}
return 0;
}
参考链接:
吉司机线段树