[bzoj十连测第三场 B]线段树

题目大意

有一个长度为n的序列与m个修改操作,每个修改操作是将序列[l,r]的元素都修改为这个区间的最大值。
现有q个操作,要么是修改序列的一个元素,要么是询问执行[l,r]的修改操作后,第k个元素是多少。询问之间独立,而修改会造成影响。

搞一搞

我们容易发现,每一个位置都可以被表示成一段区间的最大值。
我们枚举右端点r来离线做,把所有询问操作挂在其对应右端点上。
例如位置k,找到当前操作前最后一个覆盖其的操作,然后继续找最后一个覆盖该区间左端点的操作,最后一个覆盖该区间右端点的操作,最后一个覆盖最后一个覆盖该区间左端点的操作区间的左端点……
也就是,我们需要一直往左找,一直往右找,找到最左的ll与最右的rr,使得k这个位置可以表示成[ll,rr]的最大值。
把每个操作当做一个结点,我们维护两颗树:一棵叫左树一棵叫右树,左树中,一个结点的父亲所对应区间是在该结点之前的最后一个可以覆盖其左端点的区间,右树同理维护往右。倍增一下,便可以快速找到ll与rr。
如何得知最晚覆盖其的区间?可以用线段树,每加入一个区间就区间赋值。
然后,顺序扫q个操作,用线段树维护a,遇到修改就修改,遇到询问就询问对应区间最大值。

#include<cstdio>
#include<cmath>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int maxn=100000+10;
int left[maxn][25],right[maxn][25];
int a[maxn],tree[maxn*4],set[maxn*4],b[maxn][2];
bool bz[maxn*4];
struct dong{
    int l,r,k,ll,rr;
    bool p;
} ask[maxn];
int h[maxn],go[maxn],next[maxn];
int i,j,k,l,t,n,m,q,tot,top;
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*10+ch-'0';
        ch=getchar();
    }
    return x*f;
}
void add(int x,int y){
    go[++tot]=y;
    next[tot]=h[x];
    h[x]=tot;
}
void mark(int p,int x){
    bz[p]=1;
    set[p]=x;
}
void down(int p){
    if (bz[p]){
        mark(p*2,set[p]);
        mark(p*2+1,set[p]);
        bz[p]=0;
    }
}
void modify(int p,int l,int r,int a,int b,int x){
    if (l==a&&r==b){
        mark(p,x);
        return;
    }
    down(p);
    int mid=(l+r)/2;
    if (b<=mid) modify(p*2,l,mid,a,b,x);
    else if (a>mid) modify(p*2+1,mid+1,r,a,b,x);
    else{
        modify(p*2,l,mid,a,mid,x);
        modify(p*2+1,mid+1,r,mid+1,b,x);
    }
}
int get(int p,int l,int r,int a){
    if (l==r) return set[p];
    down(p);
    int mid=(l+r)/2;
    if (a<=mid) return get(p*2,l,mid,a);else get(p*2+1,mid+1,r,a);
}
void change(int p,int l,int r,int a,int b){
    if (l==r){
        tree[p]=b;
        return;
    }
    int mid=(l+r)/2;
    if (a<=mid) change(p*2,l,mid,a,b);else change(p*2+1,mid+1,r,a,b);
    tree[p]=max(tree[p*2],tree[p*2+1]);
}
int query(int p,int l,int r,int a,int b){
    if (l==a&&r==b) return tree[p];
    int mid=(l+r)/2;
    if (b<=mid) return query(p*2,l,mid,a,b);
    else if (a>mid) return query(p*2+1,mid+1,r,a,b);
    else return max(query(p*2,l,mid,a,mid),query(p*2+1,mid+1,r,mid+1,b));
}
int getleft(int x,int y){
    int j=floor(log(m)/log(2));
    while (j>=0){
        if (left[x][j]>=y) x=left[x][j];
        j--;
    }
    return x;
}
int getright(int x,int y){
    int j=floor(log(m)/log(2));
    while (j>=0){
        if (right[x][j]>=y) x=right[x][j];
        j--;
    }
    return x;
}
int main(){
    freopen("segment.in","r",stdin);freopen("segment.out","w",stdout);
    n=read();m=read();q=read();
    fo(i,1,n) a[i]=read();
    fo(i,1,m){
        b[i][0]=read();
        b[i][1]=read();
    }
    fo(i,1,q){
        t=read();
        ask[i].l=read();ask[i].r=read();
        if (t==2){
            ask[i].k=read();
            add(ask[i].r,i);
            ask[i].p=1;
        }
    }
    fo(i,1,m){
        left[i][0]=get(1,1,n,b[i][0]);
        right[i][0]=get(1,1,n,b[i][1]);
        modify(1,1,n,b[i][0],b[i][1],i);
        fo(j,1,floor(log(m)/log(2))){
            left[i][j]=left[left[i][j-1]][j-1];
            right[i][j]=right[right[i][j-1]][j-1];
        }
        t=h[i];
        while (t){
            j=get(1,1,n,ask[go[t]].k);
            if (j<ask[go[t]].l) ask[go[t]].ll=ask[go[t]].rr=ask[go[t]].k;
            else{
                ask[go[t]].ll=b[getleft(j,ask[go[t]].l)][0];
                ask[go[t]].rr=b[getright(j,ask[go[t]].l)][1];
            }
            t=next[t];
        }
    }
    fo(i,1,n) change(1,1,n,i,a[i]);
    fo(i,1,q)
        if (ask[i].p) printf("%d\n",query(1,1,n,ask[i].ll,ask[i].rr));
        else change(1,1,n,ask[i].l,ask[i].r);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值