吉老师线段树

吉如一线段树

\([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;
}

参考链接:
吉司机线段树

posted @ 2022-02-18 17:44  Arashimu  阅读(174)  评论(0编辑  收藏  举报