1. 程式人生 > >[BZOJ]5312: 冒險 線段樹 複雜度分析

[BZOJ]5312: 冒險 線段樹 複雜度分析

Description

Kaiser終於成為冒險協會的一員,這次冒險協會派他去冒險,他來到一處古墓,卻被大門上的守護神擋住了去路,守護神給出了一個問題,
只有答對了問題才能進入,守護神給出了一個自然數序列a,每次有一下三種操作。
1,給出l,r,x,將序列l,r之間的所有數都 and x
2,給出l,r,x,將序列l,r之間的所有數都 or x
3,給出l,r,詢問l,r之間的最大值

題解:

半年前不會做這道題……但是現在會做了。
我們先只考慮or操作。
定義一個線段樹節點的勢能(即操作次數)為這個區間的所有數有幾個位不是完全一樣的。
如果現在一個區間or一個數,對所有數的影響是一樣的,那麼對這個區間的操作就可以變為區間加。顯然一次or操作如果不能直接區間加,那麼這個區間至少會有一個位全部變為 1

1 ,即這個區間的勢能減少 1 1 。由於勢能是隻會遞減的,所以複雜度為 20 n 20n
。(到一個節點至少會使它的勢能 1 -1 )。
如果考慮兩種操作,勢能不再是遞減的,但是由於一次and操作只會使 l o g n
logn
個區間的勢能恢復為 20 20 ,所以總勢能即複雜度變為 O ( 20 × n + m × 20 × l o g n ) O(20\times n+m\times 20\times logn) ,可以通過。

程式碼:

#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define pa pair<int,int>
const int Maxn=200010;
const int inf=2147483647;
int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
    return x*f;
}
int n,m,a[Maxn];
struct Seg{int l,r,lc,rc,Or,And,tag,mx;}tr[Maxn<<1];
int len=0;
void Add(int x,int v)
{
    tr[x].mx+=v;tr[x].And+=v;tr[x].Or+=v;tr[x].tag+=v;
}
void up(int x)
{
    int lc=tr[x].lc,rc=tr[x].rc;
    tr[x].Or=(tr[lc].Or|tr[rc].Or);
    tr[x].And=(tr[lc].And&tr[rc].And);
    tr[x].mx=max(tr[lc].mx,tr[rc].mx);
}
void down(int x)
{
    if(tr[x].tag)Add(tr[x].lc,tr[x].tag),Add(tr[x].rc,tr[x].tag),tr[x].tag=0;
}
void build(int l,int r)
{
    int x=++len;
    tr[x].l=l;tr[x].r=r;tr[x].tag=0;
    if(l==r){tr[x].mx=tr[x].Or=tr[x].And=a[l];return;}
    int mid=l+r>>1;
    tr[x].lc=len+1,build(l,mid);
    tr[x].rc=len+1,build(mid+1,r);
    up(x);
}
int query(int x,int l,int r)
{
    if(tr[x].l==l&&tr[x].r==r)return tr[x].mx;
    int mid=tr[x].l+tr[x].r>>1,lc=tr[x].lc,rc=tr[x].rc;
    down(x);
    if(r<=mid)return query(lc,l,r);
    if(l>mid)return query(rc,l,r);
    return max(query(lc,l,mid),query(rc,mid+1,r));
}
void AND(int x,int l,int r,int v)
{
    if(l<=tr[x].l&&tr[x].r<=r)
    {
        bool flag=true;int t=0;
        for(int i=20;i>=0;i--)
        if(!((1<<i)&v))
        {
            if(!(((1<<i)&tr[x].And)||(!((1<<i)&tr[x].Or)))){flag=false;break;}
            if((1<<i)&tr[x].And)t-=(1<<i);
        }
        if(flag)
        {
            Add(x,t);
            return;
        }
    }
    int mid=tr[x].l+tr[x].r>>1,lc=tr[x].lc,rc=tr[x].rc;
    down(x);
    if(l<=mid)AND(lc,l,r,v);
    if(r>mid)AND(rc,l,r,v);
    up(x);
}
void OR(int x,int l,int r,int v)
{
    if(l<=tr[x].l&&tr[x].r<=r)
    {
        bool flag=true;int t=0;
        for(int i=20;i>=0;i--)
        if((1<<i)&v)
        {
            if(!(((1<<i)&tr[x].And)||(!((1<<i)&tr[x].Or)))){flag=false;break;}
            if(!((1<<i)&tr[x].Or))t+=(1<<i);
        }
        if(flag)
        {
            Add(x,t);
            return;
        }
    }
    int mid=tr[x].l+tr[x].r>>1,lc=tr[x].lc,rc=tr[x].rc;
    down(x);
    if(l<=mid)OR(lc,l,r,v);
    if(r>mid)OR(rc,l,r,v);
    up(x);
}
int main()
{
    n=read(),m=read();
    for(int i=1;i<=n;i++)a[i]=read();
    build(1,n);
    while(m--)
    {
        int op=read(),l=read(),r=read();
        if(op==1)AND(1,l,r,read());
        else if(op==2)OR(1,l,r,read());
        else printf("%d\n",query(1,l,r));
    }
}