Black And White【HDU3911详解】【线段树连续区间反转问题】【详细注释】

这是一道非常好的线段树区间问题,这道题的代码量偏多,但是用作找BUG也是很不错,于是乎有了:

这对于一个ACMer不是一件多么尴尬的事情,于是,我把这道题做了两遍,反复的思考了这道题后对这方面题也算有了个深刻的认识了:接下来,我会讲解一下我的思路。

题面

Problem Description

There are a bunch of stones on the beach; Stone color is white or black. Little Sheep has a magic brush, she can change the color of a continuous stone, black to white, white to black. Little Sheep like black very much, so she want to know the longest period of consecutive black stones in a range [i, j].

Input

  There are multiple cases, the first line of each case is an integer n(1<= n <= 10^5), followed by n integer 1 or 0(1 indicates black stone and 0 indicates white stone), then is an integer M(1<=M<=10^5) followed by M operations formatted as x i j(x = 0 or 1) , x=1 means change the color of stones in range[i,j], and x=0 means ask the longest period of consecutive black stones in range[i,j]

Output

When x=0 output a number means the longest length of black stones in range [i,j].

Sample Input

 

4 1 0 1 0 5 0 1 4 1 2 3 0 1 4 1 3 3 0 4 4

Sample Output

 

1 2 0

Source

2011 Multi-University Training Contest 8 - Host by HUST

思路

这道题大体思路就是一个{ Set(l, r, x),Query(l, r) }的问题,我们可以利用lazy标记来往下递归,假如我们要把这一串点做一个反转,那么把它包含的区间直接钉上lazy标记,然后可以知道若是第二次访问到这个点且刚好包含(只要包含就行),那么lazy标记相互抵消;但若是第二次访问到这个点,且不是包含关系,我们就可以pushdown()了。——按照这个思路,我们就可以继续往下更新了,以上是更新操作的难点下面讲一下查找操作的几个注意事项。

对于查找操作,我们不难想到可以通过包含关系直接返回之类的,但这里会有些小细节,譬如说我们在处理了ql>=mid+1以及qr<=mid的这两类情况后,剩下的就是mid在ql和qr之间的事了,对于这种情况,我们要考虑三点:

(一)、我们所需要的点是mid两旁的值,也就是答案在中间的情况,这个情况下,我们就需要考虑最远达到距离(mid-ql+1与trie[rt<<1].rs(左半边)qr-mid与trie[rt<<1|1].ls(右半边))与最长单边连续区间长度之间的关系,我们只能取小值,不能超出范围。

(二)、若是中间的情况并非最值,那么我们还得考虑两边的区间,譬如左半边是否存在这样的连续区间,它的长度要更加的长。

(三)、同理,我们依然得对右区间求最长连续长度。

第一次AC的代码(多亏学长给我找出的BUG)——有很详细的注释:

#include <iostream>
#include <cstring>
#include <cstdio>
#include <limits>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <map>
#include <algorithm>
#define lowbit(x) ( x&(-x) )
using namespace std;
typedef long long ll;
const int maxN=100010;
struct node
{
    int l,r;            //左、右端
    int ls,rs,ms;       //'1'区间的最大
    int lz,rz,mz;       //'0'区间的最大
}trie[maxN<<2];
int lazy[maxN<<2]={0};      //一开始并没有翻转,若遇到两次翻转,则可以抵消
int a[maxN];
int N,M;
void buildTree(int rt, int l, int r)        //建树,这里的建树与更新差不多,因为最初就有断点区间
{
    if(l==r)        //安排好条件即返回
    {
        trie[rt].l=l;                       //更新左右端点
        trie[rt].r=r;
        trie[rt].ls=trie[rt].rs=trie[rt].ms=a[l];        //‘1’连续点的长度
        trie[rt].lz=trie[rt].rz=trie[rt].mz=1-a[l];      //同时也要更新‘0’的连续长度,用作之后Lazy标记访问到后的直接交换
        return;
    }
    trie[rt].l=l;       //更新左右端点
    trie[rt].r=r;
    int mid=(l+r)>>1;
    buildTree(rt<<1, l, mid);       //先往下递归,后寻找值
    buildTree(rt<<1|1, mid+1, r);
    trie[rt].ls=trie[rt<<1].ls;     //先把左儿子的左端赋给父节点的左端
    trie[rt].rs=trie[rt<<1|1].rs;   //把右儿子的右端赋给父节点的右端
    trie[rt].lz=trie[rt<<1].lz;     //'0'节点求长
    trie[rt].rz=trie[rt<<1|1].rz;
    trie[rt].ms=max(trie[rt<<1].ms, trie[rt<<1|1].ms);      //!!!这里错了很多次——要改的应该是左右儿子的最长区间!!!
    trie[rt].ms=max(trie[rt].ms, trie[rt<<1].rs+trie[rt<<1|1].ls);      //再求加上中间后的最长连续
    trie[rt].mz=max(trie[rt<<1].mz, trie[rt<<1|1].mz);          //‘0’节点求长
    trie[rt].mz=max(trie[rt].mz, trie[rt<<1].rz+trie[rt<<1|1].lz);
    if(trie[rt].ls==trie[rt<<1].r-trie[rt<<1].l+1)      //左边是满的话,就可以往右边加了
    {
        trie[rt].ls+=trie[rt<<1|1].ls;
    }
    if(trie[rt].rs==trie[rt<<1|1].r-trie[rt<<1|1].l+1)      //右边也是满的话
    {
        trie[rt].rs+=trie[rt<<1].rs;
    }                                                   //同理可得‘0’节点
    if(trie[rt].lz==trie[rt<<1].r-trie[rt<<1].l+1)      //左边是满的话,就可以往右边加了
    {
        trie[rt].lz+=trie[rt<<1|1].lz;
    }
    if(trie[rt].rz==trie[rt<<1|1].r-trie[rt<<1|1].l+1)      //右边也是满的话
    {
        trie[rt].rz+=trie[rt<<1].rz;
    }
}
void workdown(int rt)
{
    swap(trie[rt].ls, trie[rt].lz);
    swap(trie[rt].rs, trie[rt].rz);
    swap(trie[rt].ms, trie[rt].mz);
}
void pushdown(int rt, int l, int r)
{
    if(lazy[rt])
    {
        lazy[rt<<1]^=1;
        lazy[rt<<1|1]^=1;
        workdown(rt<<1);
        workdown(rt<<1|1);
        lazy[rt]=0;
    }
}
void update(int rt, int l, int r, int ql, int qr)
{
    if(ql<=l && qr>=r)
    {
        lazy[rt]^=1;
        workdown(rt);
        return;
    }
    pushdown(rt, l, r);     //遇到非包含区间,往下递推
    int mid=(l+r)>>1;
    if(ql<=mid) update(rt<<1, l, mid, ql, qr);
    if(qr>=mid+1) update(rt<<1|1, mid+1, r, ql, qr);        //下面就是更新了
    trie[rt].ls=trie[rt<<1].ls;
    trie[rt].rs=trie[rt<<1|1].rs;
    trie[rt].lz=trie[rt<<1].lz;     //'0'节点求长
    trie[rt].rz=trie[rt<<1|1].rz;
    trie[rt].ms=max(trie[rt<<1].ms, trie[rt<<1|1].ms);      //!!!这里错了很多次——要改的应该是左右儿子的最长区间!!!
    trie[rt].ms=max(trie[rt].ms, trie[rt<<1].rs+trie[rt<<1|1].ls);
    trie[rt].mz=max(trie[rt<<1].mz, trie[rt<<1|1].mz);          //‘0’节点求长
    trie[rt].mz=max(trie[rt].mz, trie[rt<<1].rz+trie[rt<<1|1].lz);
    if(trie[rt].ls==trie[rt<<1].r-trie[rt<<1].l+1)      //左边是满的话,就可以往右边加了
    {
        trie[rt].ls+=trie[rt<<1|1].ls;
    }
    if(trie[rt].rs==trie[rt<<1|1].r-trie[rt<<1|1].l+1)      //右边也是满的话
    {
        trie[rt].rs+=trie[rt<<1].rs;
    }                                                   //同理可得‘0’节点
    if(trie[rt].lz==trie[rt<<1].r-trie[rt<<1].l+1)      //左边是满的话,就可以往右边加了
    {
        trie[rt].lz+=trie[rt<<1|1].lz;
    }
    if(trie[rt].rz==trie[rt<<1|1].r-trie[rt<<1|1].l+1)      //右边也是满的话
    {
        trie[rt].rz+=trie[rt<<1].rz;
    }
}
int Query(int rt, int l, int r, int ql, int qr)
{
    if(ql<=l && qr>=r)     //其他也可以直接返回的情况
    {
        return trie[rt].ms;
    }
    int mid=(l+r)>>1;
    pushdown(rt, l, r);
    if(qr<=mid) return Query(rt<<1, l, mid, ql, qr);
    else if(ql>mid) return Query(rt<<1|1, mid+1, r, ql, qr);
    else
    {
        int aa,bb,cc;
        aa=min(mid-ql+1, trie[rt<<1].rs)+min(qr-mid, trie[rt<<1|1].ls);
        bb=Query(rt<<1, l, mid, ql, mid);
        cc=Query(rt<<1|1, mid+1, r, mid+1, qr);
        return max(aa, max(bb, cc));        //遇到mid在ql和qr之间的情况,就要判断是中间长还是左右两个边界连续区间长
    }
}
int main()
{
    while(scanf("%d",&N)!=EOF)
    {
        memset(a, 0, sizeof(a));
        memset(trie, 0, sizeof(trie));
        memset(lazy, 0, sizeof(lazy));
        for(int i=1; i<=N; i++)
        {
            scanf("%d",&a[i]);
        }
        buildTree(1, 1, N);     //建树,接下来就是询问或者修建
        scanf("%d",&M);
        while(M--)
        {
            int e1,e2,e3;
            scanf("%d%d%d",&e1,&e2,&e3);
            if(e1)
            {
                update(1, 1, N, e2, e3);
            }
            else
            {
                printf("%d\n",Query(1, 1, N, e2, e3));
            }
        }
    }
    return 0;
}

第二次写的代码比第一次就要高大上的多,毕竟有了第一次的理解,可以作为第二次再做的同学的参考:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <limits>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <map>
#define lowbit(x) ( x&(-x) )
using namespace std;
typedef long long ll;
const int maxN=100005;
struct node
{
    int l,r;
    int ls,rs,ms;     //'1'
    int lo,ro,mo;     //'0'
}trie[maxN<<2];
int N,M;
int a[maxN];
int lazy[maxN<<2];
void work(int rt)
{
    swap(trie[rt].ls, trie[rt].lo);
    swap(trie[rt].rs, trie[rt].ro);
    swap(trie[rt].ms, trie[rt].mo);
}
void pushup(int rt, int l, int r)       //向上更新
{
    trie[rt].ls=trie[rt<<1].ls;     //'1'
    trie[rt].rs=trie[rt<<1|1].rs;
    
    trie[rt].lo=trie[rt<<1].lo;     //'0'
    trie[rt].ro=trie[rt<<1|1].ro;
    
    trie[rt].ms=max(trie[rt<<1].ms, trie[rt<<1|1].ms);      //'1'
    
    trie[rt].mo=max(trie[rt<<1].mo, trie[rt<<1|1].mo);      //'0'
    
    trie[rt].ms=max(trie[rt].ms, trie[rt<<1].rs+trie[rt<<1|1].ls);      //'1'
    
    trie[rt].mo=max(trie[rt].mo, trie[rt<<1].ro+trie[rt<<1|1].lo);      //'0'
    
    if(trie[rt].ls==trie[rt<<1].r-trie[rt<<1].l+1) trie[rt].ls+=trie[rt<<1|1].ls;       //'1'
    if(trie[rt].rs==trie[rt<<1|1].r-trie[rt<<1|1].l+1) trie[rt].rs+=trie[rt<<1].rs;
    
    if(trie[rt].lo==trie[rt<<1].r-trie[rt<<1].l+1) trie[rt].lo+=trie[rt<<1|1].lo;       //'0'
    if(trie[rt].ro==trie[rt<<1|1].r-trie[rt<<1|1].l+1) trie[rt].ro+=trie[rt<<1].ro;
}
void pushdown(int rt, int l, int r)     //到点更新,即如果lazy标记到了就更新
{
    if(lazy[rt])
    {
        lazy[rt<<1]^=1;
        lazy[rt<<1|1]^=1;
        work(rt<<1);
        work(rt<<1|1);
        lazy[rt]=0;
    }
}
void buildTree(int rt, int l, int r)
{
    if(l==r)
    {
        trie[rt].l=trie[rt].r=l;
        trie[rt].ls=trie[rt].rs=trie[rt].ms=a[l];
        trie[rt].lo=trie[rt].ro=trie[rt].mo=1-a[l];
        return;
    }
    trie[rt].l=l;
    trie[rt].r=r;
    int mid=(l+r)>>1;
    buildTree(rt<<1, l, mid);
    buildTree(rt<<1|1, mid+1, r);
    pushup(rt, l, r);
}
void update(int rt, int l, int r, int ql, int qr)
{
    if(ql<=l && qr>=r)
    {
        lazy[rt]^=1;
        work(rt);
        return;
    }
    int mid=(l+r)>>1;
    pushdown(rt, l, r);
    if(ql<=mid) update(rt<<1, l, mid, ql, qr);
    if(qr>=mid+1) update(rt<<1|1, mid+1, r, ql, qr);
    pushup(rt, l, r);
}
int Query(int rt, int l, int r, int ql, int qr)
{
    if(ql<=l && qr>=r)
    {
        return trie[rt].ms;
    }
    int mid=(l+r)>>1;
    pushdown(rt, l, r);
    if(qr<=mid) return Query(rt<<1, l, mid, ql, qr);
    else if(ql>=mid+1) return Query(rt<<1|1, mid+1, r, ql, qr);
    else
    {
        int aa=0,bb=0,cc=0;
        aa=min(mid-ql+1, trie[rt<<1].rs)+min(qr-mid, trie[rt<<1|1].ls);
        bb=Query(rt<<1, l, mid, ql, mid);
        cc=Query(rt<<1|1, mid+1, r, mid+1, qr);
        return max(aa, max(bb, cc));
    }
}
int main()
{
    while(scanf("%d",&N)!=EOF)
    {
        memset(a, 0, sizeof(a));
        memset(lazy, 0, sizeof(lazy));
        memset(trie, 0, sizeof(trie));
        for(int i=1; i<=N; i++) scanf("%d",&a[i]);
        buildTree(1, 1, N);
        scanf("%d",&M);
        while(M--)
        {
            int e1,e2,e3;
            scanf("%d%d%d",&e1,&e2,&e3);
            if(e1) update(1, 1, N, e2, e3);
            else printf("%d\n",Query(1, 1, N, e2, e3));
        }
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_41730082/article/details/81320212