[NOI2005]维护数列,洛谷P2042,splay+lazy

本文介绍如何使用伸展树实现区间操作,包括区间赋值、区间反转和区间最大值求解等高级操作,并详细解释了相关代码实现。

正题

      是不是觉得标题好奇特,我们想想区间操作怎么用伸展树来实现。

      首先我们把左端点的前一个移到根,再把右端点的后一个移到根的右儿子,那么现在根的右儿子的左子树就是我们要求的区间。如下图,我们要求的是[2,4]。那么就把1移到根,5移到右儿子即可。


      我们现在已经知道要处理的是哪一棵子树了,我们就可以在这棵子树的根节点打一个标记,表示这棵子树将要被做什么操作。当然,我们要加上一个最前节点和最后节点,来保证整个区间操作时有前一个和后一个。

make_same:

void make_same(int x,int tt,int c){
    int ip1=findip(x);//我们先把x的前一个旋到根(其实找的是x-1这个点,但是因为有一个最前节点,所以要往后挪一位)
    int ip2=findip(x+tt+1);//找出右节点的下一个
    splay(ip1,0);splay(ip2,ip1);//并旋到根
    int ip3=s[ip2].son[0];
    s[ip3].c=c;//初始化,先把该子树的根节点搞定
    s[ip3].same=c;
    s[ip3].res=0;//翻转标记可有可无,因为整段区间都是一样的
    s[ip3].sum=c*s[ip3].num;//初始化sum
    if(c>=0) s[ip3].lmax=s[ip3].rmax=s[ip3].mmax=s[ip3].sum;//后面再讲。
    else s[ip3].lmax=s[ip3].rmax=0,s[ip3].mmax=s[ip3].same;
    update(ip2);update(ip1);//更新父节点和根
}

reverse:

void reverse(int x,int tt){
    int ip1=findip(x);//也是先把区间子树处理出来
    int ip2=findip(x+tt+1);
    splay(ip1,0);splay(ip2,ip1);
    int ip3=s[ip2].son[0];
    if(s[ip3].same==-1e9){//如果这段区间一样,那么打不打标记都无所谓
    	swap(s[ip3].son[0],s[ip3].son[1]);//交换左右儿子,打标记意味着我做了我的子树没做
   	swap(s[ip3].lmax,s[ip3].rmax);//后面再说
   	s[ip3].res^=1;//翻转一遍
    }
	update(ip2);update(ip1);//更新父节点和根
}

这里是看下面:

      区间求和相当于上面的操作也变得容易了,那么max_sum怎么做呢emm?

      对于每个节点,和他所管理的一个区间(子树),都维护一个lmax和rmax.

      分别代表着前缀最大值或后缀最大值,当为负数时都为0(不选)。

      那么一个节点所管理区间的区间最大值就是:

      1.左儿子的最大值。

      2.右儿子的最大值。

      3.左儿子的rmax+该节点值+右儿子的lmax.

s[x].mmax=max(s[s[x].son[0]].mmax,max(s[s[x].son[1]].mmax,s[s[x].son[0]].rmax+s[x].c+s[s[x].son[1]].lmax));

      同时我们要更新当前区间的最大前缀和最大后缀。

      1.选左儿子的最大前缀。

      2.选全部左儿子+自己+右边的最大前缀

      这样可以包含所有情况。

      后缀也是一样的

    s[x].lmax=max(s[s[x].son[0]].lmax,s[s[x].son[0]].sum+s[x].c+s[s[x].son[1]].lmax);
    s[x].rmax=max(s[s[x].son[1]].rmax,s[s[x].son[1]].sum+s[x].c+s[s[x].son[0]].rmax);

       那我们什么时候才去更新儿子呢?

      很明显是我们需要经过当前点的时候,我们再找一个点的位置的时候,路上经过的点就是splay的时候可能会改变的点哦~

      所以我们需要在findip的时候往下推即可

void pushdown(int x){
    if(s[x].same!=-1e9){
        int l=s[x].son[0],r=s[x].son[1];
        if(l!=0) s[l].c=s[l].same=s[x].same;
        if(r!=0) s[r].c=s[r].same=s[x].same;
        if(l!=0) s[l].sum=s[l].num*s[l].c;
        if(r!=0) s[r].sum=s[r].num*s[r].c;
        if(s[x].same>=0){
            if(l!=0) s[l].mmax=s[l].lmax=s[l].rmax=s[l].sum;
            if(r!=0) s[r].mmax=s[r].lmax=s[r].rmax=s[r].sum;
        }
        else{
            if(l!=0) s[l].lmax=s[l].rmax=0,s[l].mmax=s[l].same;
            if(r!=0) s[r].lmax=s[r].rmax=0,s[r].mmax=s[r].same;
        }
        s[x].same=-1e9;
    }
    if(s[x].res!=0){
        s[s[x].son[0]].res^=1;s[s[x].son[1]].res^=1;
        s[x].res=0;
        swap(s[s[x].son[0]].lmax,s[s[x].son[0]].rmax);
        swap(s[s[x].son[1]].lmax,s[s[x].son[1]].rmax);
        swap(s[s[x].son[0]].son[0],s[s[x].son[0]].son[1]);
        swap(s[s[x].son[1]].son[0],s[s[x].son[1]].son[1]);
    }
}

int findip(int x){
    int now=root;
    while(1){
    	pushdown(now);
        if(x<=s[s[now].son[0]].num) now=s[now].son[0];
        else if(s[s[now].son[0]].num+1<x) x=x-s[s[now].son[0]].num-1,now=s[now].son[1];
        else break;
    }
    return now;
}

      最后,一开始建树的时候要二分建树来保证自己不被卡常emmm。

void build(int x,int y,int t){
    int mid=(x+y)/2;
    s[t].c=a[mid];
    s[t].same=-1e9;s[t].res=0;
    s[t].son[0]=s[t].son[1]=0;s[t].num=0;s[t].sum=a[mid];s[t].mmax=-1e9;
    s[t].lmax=s[t].rmax=0;
    int op;
    if(x<mid) {
        op=f.front();f.pop();
        s[t].son[0]=op;
        s[op].f=t;
        build(x,mid-1,op);
    }
    if(mid<y){
        op=f.front();f.pop();
        s[t].son[1]=op;
        s[op].f=t;
        build(mid+1,y,op);
    }
    update(t);
}
s[0].mmax=a[0]=a[n+2]=1e9;

当然为了保证我们加进去的点不被选中,要加上一句话。(就这样被卡了2天)

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<queue>
using namespace std;

int n,m;
int a[1000017];
struct tree{
    int son[2],lmax,rmax,mmax,f,sum,num,c;
    int res,same;
}s[1000017];
int root=0;
queue<int> f;

void update(int x){
    s[x].num=s[s[x].son[0]].num+s[s[x].son[1]].num+1;
    s[x].sum=s[s[x].son[0]].sum+s[s[x].son[1]].sum+s[x].c;
    s[x].lmax=max(s[s[x].son[0]].lmax,s[s[x].son[0]].sum+s[x].c+s[s[x].son[1]].lmax);
    s[x].rmax=max(s[s[x].son[1]].rmax,s[s[x].son[1]].sum+s[x].c+s[s[x].son[0]].rmax);
    s[x].mmax=max(s[s[x].son[0]].mmax,max(s[s[x].son[1]].mmax,s[s[x].son[0]].rmax+s[x].c+s[s[x].son[1]].lmax));
}

void pushdown(int x){
    if(s[x].same!=-1e9){
        int l=s[x].son[0],r=s[x].son[1];
        if(l!=0) s[l].c=s[l].same=s[x].same;
        if(r!=0) s[r].c=s[r].same=s[x].same;
        if(l!=0) s[l].sum=s[l].num*s[l].c;
        if(r!=0) s[r].sum=s[r].num*s[r].c;
        if(s[x].same>=0){
            if(l!=0) s[l].mmax=s[l].lmax=s[l].rmax=s[l].sum;
            if(r!=0) s[r].mmax=s[r].lmax=s[r].rmax=s[r].sum;
        }
        else{
            if(l!=0) s[l].lmax=s[l].rmax=0,s[l].mmax=s[l].same;
            if(r!=0) s[r].lmax=s[r].rmax=0,s[r].mmax=s[r].same;
        }
        s[x].same=-1e9;
    }
    if(s[x].res!=0){
        s[s[x].son[0]].res^=1;s[s[x].son[1]].res^=1;
        s[x].res=0;
        swap(s[s[x].son[0]].lmax,s[s[x].son[0]].rmax);
        swap(s[s[x].son[1]].lmax,s[s[x].son[1]].rmax);
        swap(s[s[x].son[0]].son[0],s[s[x].son[0]].son[1]);
        swap(s[s[x].son[1]].son[0],s[s[x].son[1]].son[1]);
    }
}

int findip(int x){
    int now=root;
    while(1){
    	pushdown(now);
        if(x<=s[s[now].son[0]].num) now=s[now].son[0];
        else if(s[s[now].son[0]].num+1<x) x=x-s[s[now].son[0]].num-1,now=s[now].son[1];
        else break;
    }
    return now;
}

void build(int x,int y,int t){
    int mid=(x+y)/2;
    s[t].c=a[mid];
    s[t].same=-1e9;s[t].res=0;
    s[t].son[0]=s[t].son[1]=0;s[t].num=0;s[t].sum=a[mid];s[t].mmax=-1e9;
    s[t].lmax=s[t].rmax=0;
    int op;
    if(x<mid) {
        op=f.front();f.pop();
        s[t].son[0]=op;
        s[op].f=t;
        build(x,mid-1,op);
    }
    if(mid<y){
        op=f.front();f.pop();
        s[t].son[1]=op;
        s[op].f=t;
        build(mid+1,y,op);
    }
    update(t);
}

void rotate(int x,int w){
    int f=s[x].f,ff=s[f].f;
    s[f].son[1-w]=s[x].son[w];
    if(s[x].son[w]!=0) s[s[x].son[w]].f=f;
    if(s[ff].son[0]==f) s[ff].son[0]=x;
    else s[ff].son[1]=x;
    s[x].f=ff;
    s[f].f=x;
    s[x].son[w]=f;
    update(f);
    update(x);
}

void splay(int x,int tar){
    while(s[x].f!=tar){
        int f=s[x].f,ff=s[f].f;
        if(ff==tar){
            if(s[f].son[0]==x) rotate(x,1);
            else rotate(x,0);
        }
        else{
            if(s[ff].son[0]==f){
                if(s[f].son[0]==x) {rotate(f,1);rotate(x,1);}
                else {rotate(x,0);rotate(x,1);}
            }
            else{
                if(s[f].son[0]==x) {rotate(x,1);rotate(x,0);}
                else {rotate(f,0);rotate(x,0);}
            }
        }
    }
    if(tar==0) root=x;
}

void insert(int x,int tot){
    int op=f.front();
    f.pop();
    build(1,tot,op);
    int ip1=findip(x+1);
    int ip2=findip(x+2);
    splay(ip1,0);
    splay(ip2,ip1);
    s[ip2].son[0]=op;
    s[op].f=ip2;
    update(ip2);
    update(ip1);
}

void recycle(int x){
    if(s[x].son[0]) recycle(s[x].son[0]);
    if(s[x].son[1]) recycle(s[x].son[1]);
    f.push(x);
    s[x].son[0]=s[x].son[1]=0;s[x].num=s[x].sum=0;
    s[x].c=0;s[x].f=0;s[x].same=-1e9;s[x].mmax=-1e9;
}

void del(int x,int tt){
    int ip1=findip(x);
    int ip2=findip(x+tt+1);
    splay(ip1,0);splay(ip2,ip1);
    recycle(s[ip2].son[0]);
    s[ip2].son[0]=0;
    update(ip2);
    update(ip1);
}

void make_same(int x,int tt,int c){
    int ip1=findip(x);
    int ip2=findip(x+tt+1);
    splay(ip1,0);splay(ip2,ip1);
    int ip3=s[ip2].son[0];
    s[ip3].c=c;
    s[ip3].same=c;
    s[ip3].res=0;
    s[ip3].sum=c*s[ip3].num;
    if(c>=0) s[ip3].lmax=s[ip3].rmax=s[ip3].mmax=s[ip3].sum;
    else s[ip3].lmax=s[ip3].rmax=0,s[ip3].mmax=s[ip3].same;
    update(ip2);update(ip1);
}

void reverse(int x,int tt){
    int ip1=findip(x);
    int ip2=findip(x+tt+1);
    splay(ip1,0);splay(ip2,ip1);
    int ip3=s[ip2].son[0];
    if(s[ip3].same==-1e9){
    	swap(s[ip3].son[0],s[ip3].son[1]);
   		swap(s[ip3].lmax,s[ip3].rmax);
   		s[ip3].res^=1;
    }
	update(ip2);update(ip1);
}

int get_tot(int x,int tt){
    int ip1=findip(x);
    int ip2=findip(x+tt+1);
    splay(ip1,0);splay(ip2,ip1);
    update(ip2);
    update(ip1);
    return s[s[ip2].son[0]].sum;
}

int main(){
	s[0].mmax=-1e9;
    s[0].lmax=s[0].rmax=s[0].sum=s[0].num=0;
    scanf("%d %d",&n,&m);
    a[1]=a[n+2]=-1e9;
    for(int i=1;i<=1000002;i++) f.push(i);
    for(int i=2;i<=n+1;i++)
        scanf("%d",&a[i]);
    int op=f.front();
    root=op;
    f.pop();
    build(1,n+2,op);
    char c[20];
    int posi,tot,cc;
    for(int i=1;i<=m;i++){
    	scanf("%s",c);
        if(!(c[0]=='M' && c[1]=='A' && c[2]=='X')) scanf("%d %d",&posi,&tot);
        if(c[0]=='I'){
            for(int j=1;j<=tot;j++)
                scanf("%d",&a[j]);
            insert(posi,tot);
        }
        else if(c[0]=='D') del(posi,tot);
        else if(c[0]=='M' && c[1]=='A' && c[2]=='K'){
            scanf("%d",&cc);	
            make_same(posi,tot,cc);
        }
        else if(c[0]=='R') reverse(posi,tot);
        else if(c[0]=='G') printf("%d\n",get_tot(posi,tot));
        else {
        	//printf("%d %d\n",s[s[root].son[0]].mmax,s[s[root].son[1]].mmax);
			printf("%d\n",s[root].mmax);
        }
    }
}










<think>嗯,用户想了解如何用分块方法解决洛谷P2042这个维护数列的问题。首先,我需要回忆一下分块算法的基本概念。分块通常是将数据分成多个块,每个块单独处理,平衡查询和修改的时间复杂度。不过,题目是NOI2005的,可能比较复杂,因为维护数列通常涉及插入、删除、区间修改、翻转、求和、最大子段和等操作。分块处理这些操作可能比较麻烦,尤其是动态的插入和删除,会导致块的大小变化,影响效率。 首先,我得确认分块是否适合这个问题。一般来说,分块的时间复杂度是O(n√n),对于大数可能还行,但像这种NOI题目可能数据量较大,分块可能不够高效,不过用户明确问分块方法,所以需要找到如何结构设计。 维护数列的典型操作包括:在某个位置插入一段数列,删除某个区间,区间覆盖,区间翻转,区间求和,求最大子段和。分块的话,每个块需要维护这些操作的信息。比如每个块需要记录翻转标记、覆盖标记,以及各种区间和、最大子段和等。 接下来,分块的结构设计。每个块可以存储一个数组,同时维护块内的总和、最大前缀和、最大后缀和、最大子段和,以及翻转和覆盖的懒标记。插入和删除操作需要考虑块的分裂和合并,这可能比较复杂,因为频繁的插入删除会导致块的大小不均,影响效率。所以可能需要设置块的大小阈值,比如当块的大小超过2√n时分裂,小于√n/2时合并相邻块。 处理翻转操作时,每个块维护一个翻转标记,当需要翻转区间时,对覆盖的完整块标记翻转,并处理部分块。同样,覆盖操作需要懒标记,当块被完全覆盖时,直接更新块内的值,并记录覆盖值。 最大子段和的计算需要每个块维护内部的最大子段和、最大前缀和、最大后缀和,这样在查询时,可以将多个块的信息合并起来得到整体的最大子段和。这可能比较复杂,需要考虑块之间的前后缀如何组合。 插入和删除操作需要找到对应的位置所在的块,如果插入导致块过大,就分裂成两个块。删除时如果块变得过小,可能需要合并相邻块。这些操作需要维护块之间的链表结构,方便快速插入和删除块。 举个例子,当要在位置pos插入一段数列,首先找到pos所在的块,如果块的大小加上插入元素后超过阈值,就分裂该块为两个,然后将插入的元素作为新块插入到链表中。这样插入的时间复杂度是O(√n),因为分裂和插入块的时间与块的大小有关。 对于翻转和覆盖操作,应用懒标记是关键,这样可以延迟实际的操作,直到需要访问块的具体内容时才进行实际的处理。例如,当需要访问一个带有翻转标记的块时,先执行翻转操作,并清除标记。 不过分块在处理这些动态操作时,尤其是频繁的插入和删除,可能效率不如平衡树结构(如Splay或Treap),但分块实现起来可能相对简单一些。需要权衡实现复杂度和效率。 总结一下,分块实现的关键点包括: 1. 分块的结构设计,每个块维护必要的信息和懒标记。 2. 插入和删除时的块分裂与合并策略。 3. 懒标记的处理,如翻转和覆盖,何时下传。 4. 查询操作时如何合并各个块的信息,如最大子段和。 可能遇到的问题包括块的大小管理不当导致性能下降,懒标记处理错误导致数据不一致,以及在合并块信息时逻辑错误。需要详细设计每个操作的步骤,并处理各种边界情况,比如操作区间跨多个块,或者在块的开头或结尾的部分操作。 最后,参考用户提供的引用,尤其是引用[1]提到递归图的理解,可能分块的递归处理不太相关,但引用[2]提到洛谷题目,可能需要参考类似题解中的分块实现方法。不过用户提供的引用可能没有直接相关的内容,所以需要依靠已有的分块知识来构建解决方案。</think>### 分块方法实现洛谷 P2042 维护数列问题 #### 分块结构设计 1. **块内信息**: - 数据存储:每个块存储$size \leq 2\sqrt{n}$个元素 - 维护以下字段: ```text sum: 区间和 lmax: 最大前缀和 rmax: 最大后缀和 tmax: 最大子段和 rev: 翻转标记(0/1) cov: 覆盖标记(特殊值表示未覆盖) ``` 2. **分块逻辑**: - 块间通过链表或数组连接 - 插入/删除时动态调整块大小,触发分裂或合并(例如块大小超过$2\sqrt{n}$时分裂,相邻块总大小低于$\sqrt{n}$时合并) #### 核心操作实现 **1. 区间翻转(REVERSE)**: - 对完整覆盖的块:翻转标记`rev ^= 1`,交换`lmax`与`rmax` - 对部分覆盖的块:下传标记后暴力翻转元素 时间复杂度:$O(\sqrt{n})$ **2. 区间覆盖(MAKE-SAME)**: - 对完整覆盖的块:更新`cov`标记,直接计算`sum`,`lmax`,`rmax`,`tmax` - 对部分覆盖的块:下传标记后暴力修改 时间复杂度:$O(\sqrt{n})$ **3. 最大子段和(MAX-SUM)**: - 合并相邻块的`rmax`和`lmax`,遍历所有块维护全局最大值 时间复杂度:$O(\sqrt{n})$ **4. 插入(INSERT)**: - 定位插入位置所在块,若插入后块大小超过阈值则分裂 时间复杂度:$O(\sqrt{n} + k/\sqrt{n})$ **5. 删除(DELETE)**: - 移除目标区间涉及的所有块,合并相邻小块 时间复杂度:$O(\sqrt{n} + k/\sqrt{n})$ #### 代码框架示例(关键部分) ```python class Block: def __init__(self): self.data = [] self.sum = 0 self.lmax = self.rmax = self.tmax = -inf self.rev = 0 self.cov = None def push_down(self): # 下传覆盖/翻转标记到数据层 if self.cov is not None: self.data = [self.cov] * len(self.data) if self.rev: self.data.reverse() self.rev = 0 self.cov = None def update_info(self): # 重新计算sum/lmax/rmax/tmax pass class BlockList: def __init__(self): self.blocks = [] def split(self, idx): # 分裂过大的块 pass def merge(self): # 合并过小的相邻块 pass ``` #### 复杂度分析 | 操作类型 | 时间复杂度 | |----------------|---------------------| | 插入/删除 | $O(\sqrt{n} + k)$ | | 翻转/覆盖/求和 | $O(\sqrt{n})$ | | 最大子段和 | $O(\sqrt{n})$ | #### 注意事项 1. **标记下传策略**:在访问块内数据前必须下传所有标记 2. **块大小平衡**:通过动态分裂/合并保证$size \in [\sqrt{n}/2, 2\sqrt{n}]$ 3. **边界处理**:特别注意区间跨多个块时的部分覆盖情况
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值