P6492 [COCI2010-2011#6] STEP

原题链接  https://www.luogu.com.cn/problem/P6492

题解

首先题目中的 L 和 R 我们可以分别用 0 和 1来代替; 

一个很自然的想法是用线段树维护答案区间的左右端点;

思路简单暴力,但是合并信息的时候需要考虑的情况较多,且复杂度较高,会T; 

这里贴上我这个思路的代码吧(16pts) :

#include<iostream>
#include<cstdio>
using namespace std;
const int N=2e5+5;
int n,m;
int qwq[N];
struct node
{
    int l,r,nl,nr;  //[l,r]是答案区间,[nl,nr]是范围区间 
}a[N<<2];
void up(int node)
{
    int mid=(a[node].nl+a[node].nr)/2;
    if(qwq[mid]^qwq[mid+1]) 
    {
        if(a[node<<1].r+1==a[node<<1|1].l)
        {
            a[node].l=a[node<<1].l;
            a[node].r=a[node<<1|1].r;
            return ;
        }
        else
        {
            int len=max(a[node<<1].r-a[node<<1].l+1,a[node<<1|1].r-a[node<<1|1].l+1);
            if(a[node<<1|1].l-a[node<<1].r-1>len)
            {
                int L,LL,R,RR;
                L=LL=R=RR=a[node<<1].r+1;
                for(int i=a[node<<1].r+2;i<a[node<<1|1].l;i++)
                {
                    if(qwq[i]^qwq[i-1]) R++;
                    else
                    {
                        if(R-L+1>RR-LL+1)
                        {
                            LL=L;
                            RR=R;
                        }
                        L=R=i;
                    }
                }
                if(RR-LL+1>len)
                {
                    a[node].l=LL;
                    a[node].r=RR;
                }
                else
                {
                    if(a[node<<1].r-a[node<<1].l+1>a[node<<1|1].r-a[node<<1|1].l+1)
                    {
                        a[node].l=a[node<<1].l;
                        a[node].r=a[node<<1].r;
                        return ;
                    }
                    else
                    {
                        a[node].l=a[node<<1|1].l;
                        a[node].r=a[node<<1|1].r;
                        return ;
                    }
                }
            }
        }
    }
    else
    {
        if(a[node<<1].r-a[node<<1].l>a[node<<1|1].r-a[node<<1|1].l)
        {
            a[node].l=a[node<<1].l;
            a[node].r=a[node<<1].r;
            return ;
        }
        else
        {
            a[node].l=a[node<<1|1].l;
            a[node].r=a[node<<1|1].r;
            return ;
        }
    }
}
void build(int node,int l,int r)
{
    a[node].nl=l;a[node].nr=r;
    if(l==r) 
    {
        a[node].l=l;
        a[node].r=r;
        return ;
    }
    int mid=(l+r)/2;
    build(node<<1,l,mid);
    build(node<<1|1,mid+1,r);
    up(node);
}
void change(int node,int l,int r,int x)
{
    if(l==r&&l==x)
    {
        qwq[x]^=1;
        if(a[node].l==a[node].r)
        {
            a[node].l=max(a[node].nl,x-1);
            a[node].r=min(a[node].nr,x+1);
            return ;
        }
        if(x==a[node].l-1&&qwq[x]^qwq[x+1]) a[node].l--;
        if(x==a[node].r+1&&qwq[x]^qwq[x-1]) a[node].r++;
        if(a[node].l<=x&&x<=a[node].r)
        {
            if(x-l>r-x) a[node].r=x-1;
            else a[node].l=x+1;
        }
        return ;
    }
    int mid=(l+r)/2;
    if(x<=mid) change(node<<1,l,mid,x);
    else change(node<<1|1,mid+1,r,x);
    up(node);
}
int main()
{
    scanf("%d %d",&n,&m);
    build(1,1,n);
    for(int i=1;i<=m;i++) 
    {
        int x;
        scanf("%d",&x);
        change(1,1,n,x);
        printf("%d\n",a[1].r-a[1].l+1);
    }
    return 0;
}
View Code

巧妙的思路

先介绍一下代码里的数组:

len [ i ]:线段树中第 i 个节点所维护的区间的长度;

L [ i ]:线段树中第 i 个节点所维护的区间的左端点;

R [ i ]:线段树中第 i 个节点所维护的区间的右端点;

S [ i ]:线段树中第 i 个节点所维护的区间的最左端开始最长的符合条件的区间长度(前缀);

H [ i ]:线段树中第 i 个节点所维护的区间的最右端开始最长的符合条件的区间长度(后缀);

ans [ i ]:线段树中第 i 个节点所维护的区间的最长符合条件的区间长度;

这样搞有什么好处呢?

可以 O ( 1 ) 完成信息的维护操作;

重点来考虑一下 pushup 的操作怎么搞,我们可以将其分为两种情况:

①. 当前区间的左儿子的右端点与右儿子的左端点相同:

这种情况下大区间的最长符合条件的区间只可能是两个子区间中的最大值:

②. 当前区间的左儿子的右端点与右儿子的左端点不同:

这种情况相比于上一种情况来说要多考虑一种情况:

就是左区间靠右的那一部分加上右区间靠左的那一部分所形成的新的符合条件的区间可能比左右区间中的都大 。

虽然这种情况看起来比较毒瘤,但是利用我们的 S 数组和 H 数组就轻松解决问题了;

所拼成的区间的长度就是 H [ node << 1 (左儿子编号) ] + S [ node<<1 | 1 (右儿子编号) ];

if(R[node<<1]^L[node<<1|1])             //如果左区间的右端点和右区间的左端点不同 
{
    ans[node]=H[node<<1]+S[node<<1|1];  //考虑两端合并后的符合条件的区间长度 
    ans[node]=max(ans[node],ans[node<<1]);  //与左区间的进行比较 
    ans[node]=max(ans[node],ans[node<<1|1]);//与右区间的进行比较 
}
else ans[node]=max(ans[node<<1],ans[node<<1|1]);  //否则只用考虑左右区间中最长的区间

接下来考虑怎么维护 S 和 H 数组,也是分两种情况来考虑: 

①. S 和 H 数组所维护的区间长度小于当前区间长度:

像上面左区间的 S 并没有覆盖整个左区间,所以更新完信息后大区间的 S 就是左区间的 S 即可; 

维护右区间的 H 同理; 

②. S 和 H 数组所维护的区间长度等于当前区间长度:

此时有 S [ node<<1 ] = len [ node<< 1 ] ;

为什么要把这种情况单独拿出来考虑呢?因为它毒瘤qwq 。

如果此时有 R [ node<<1 ] ^ L [ node<<1 | 1 ] = 1(左区间的右端点与右区间的左端点不同),那么两段可以合成一个新段来作为大区间的 S:

 维护右区间的 H 同理;

if(S[node<<1]==len[node<<1]&&R[node<<1]^L[node<<1|1]) S[node]=S[node<<1]+S[node<<1|1];  //左区间的S包含整个区间并且左区间的右端点与右区间的左端点不同,则两个区间的S合并成大区间的S 
else S[node]=S[node<<1];                //否则就继承左区间的S             
if(H[node<<1|1]==len[node<<1|1]&&R[node<<1]^L[node<<1|1]) H[node]=H[node<<1|1]+H[node<<1]; //右区间的H包含整个区间并且左区间的右端点与右区间的左端点不同,则两个区间的H合并成大区间的H 
else H[node]=H[node<<1|1];              //否则就继承右区间的H 

pushup 操作讲完了,这个题就讲完了~

Code:

#include<iostream>
#include<cstdio>
using namespace std;
const int N=8e5+5;
int n,m;
int S[N],H[N],L[N],R[N],len[N],ans[N];
void work(int node,int k)                   //将node节点的值赋值为k,并更新一下该点信息 
{
    ans[node]=S[node]=H[node]=1;
    L[node]=R[node]=k;
}
void pushup(int node)
{
    if(R[node<<1]^L[node<<1|1])             //如果左区间的右端点和右区间的左端点不同 
    {
        ans[node]=H[node<<1]+S[node<<1|1];  //考虑两端合并后的符合条件的区间长度 
        ans[node]=max(ans[node],ans[node<<1]);  //与左区间的进行比较 
        ans[node]=max(ans[node],ans[node<<1|1]);//与右区间的进行比较 
    }
    else ans[node]=max(ans[node<<1],ans[node<<1|1]);  //否则只用考虑左右区间中最长的区间 
    L[node]=L[node<<1];R[node]=R[node<<1|1];//L和R数组的维护显而易见   
    if(S[node<<1]==len[node<<1]&&R[node<<1]^L[node<<1|1]) S[node]=S[node<<1]+S[node<<1|1];  //左区间的S包含整个区间并且左区间的右端点与右区间的左端点不同,则两个区间的S合并成大区间的S 
    else S[node]=S[node<<1];                //否则就继承左区间的S             
    if(H[node<<1|1]==len[node<<1|1]&&R[node<<1]^L[node<<1|1]) H[node]=H[node<<1|1]+H[node<<1]; //右区间的H包含整个区间并且左区间的右端点与右区间的左端点不同,则两个区间的H合并成大区间的H 
    else H[node]=H[node<<1|1];              //否则就继承右区间的H 
}
void build(int node,int l,int r)            
{
    len[node]=r-l+1;                        //区间长度 
    if(l==r)                                //递归到了叶子节点 
    {
        work(node,0);                       //将该点赋值0,并更新一下该点信息 
        return ;
    }
    int mid=(l+r)/2;
    build(node<<1,l,mid);
    build(node<<1|1,mid+1,r);
    pushup(node);
}
void change(int node,int l,int r,int x)
{
    if(l==r)                                //递归到了叶子节点 
    {
        work(node,!L[node]);                //将该节点赋值为!L[node],从而达到0变成1,1变成0的效果 
        return ;
    }
    int mid=(l+r)/2;
    if(x<=mid) change(node<<1,l,mid,x);
    else change(node<<1|1,mid+1,r,x);
    pushup(node);
}
int main()
{
    scanf("%d %d",&n,&m);
    build(1,1,n);                           //建树     
    for(int i=1;i<=m;i++)
    {
        int x;
        scanf("%d",&x);
        change(1,1,n,x);                    //单点修改 
        printf("%d\n",ans[1]);              //每次要输出最长的符合条件的区间 
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/xcg123/p/13170192.html