线段树:CDOJ1592-An easy problem B (线段树的区间合并)

An easy problem B

Time Limit: 2000/1000MS (Java/Others) Memory Limit: 65535/65535KB (Java/Others)

Problem Description

N个数排成一列,每个数的大小为1或者0。有两种操作,第一种操作是把一段区间内的每个数异或1,第二种操作是询问区间内最长连续1的长度。

Input

第一行一个整数N(1≤N≤100000),表示N个数。第二行N个数。接下来一行一个整数M(1≤M≤100000),表示M个操作,接下来M行每行三个整数K,L,R。K=1表示把L到R这段区间的数全部异或上1,K=0表示询问L到R这段区间内最长连续1的长度。

Output

对于每个询问,输出对应的答案,每个询问占一行。

Sample Input

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

Sample Output

1
2
4


解题心得:

  1. 这个题考的就是一个线段树的区间合并问题外加一个lazy标记。其实这个题比较烦,要分比较多的情况,还很容易写bug。
  2. 先不说区间里面的0-1反转问题,就只是说建树和区间合并。首先要将区间合并起来(也就是向上更新的部分pushup),就要看区间合并包括几个部分。第一个,两个小的区间合并成一个大的区间,大的区间左方的最长连续1的部分就是这个大区间左子树的左方连续1部分,但是还没完,有一种特殊情况,当左子树全是1的时候大的区间的左方连续1是左子树的长度加上右子树的左方连续1的长度。大区间右方连续1的部分和左方的算法一样。还有就是大的区间的中间的连续的最大的连续1的长度,它由左子树的右方连续1的长度加上右子树左方连续1的长度。最后将三个最长的连续1区间比较一下得出最长的连续1区间。
  3. 那么我们在树中就要维护,当前节点(线段)的长度(r-l+1),l和r的位置,最长的左、右连续1的最长长度,当前最长的连续1的最长长度,以及一个lazy标记,同时,既然最后还要0-1反转,那么不但要记录1还要记录0,这样在0-1反转的时候直接将记录的0和1的情况直接交换就行了。
  4. 最后说说lazy标记的问题,当要对当前的区间进行更新的时候,要将当前区间更新,将当前区间lazy标记,并不是只标记而不进行更新。

#include
using namespace std;
const int maxn = 1e5+100;
struct node
{
    int lsum1,rsum1,lsum2,rsum2,l,r,sum1,sum2,lazy,len;//sum1代表1的情况,sum2代表0的情况
} tree[maxn<<2];
int n,m;

//lazy标记下移,0-1反转
void pushdown(int root)
{
    if(!tree[root].lazy)
        return;
    tree[root<<1].lazy ^= 1;
    tree[root<<1|1].lazy ^= 1;
    swap(tree[root<<1].sum1,tree[root<<1].sum2);
    swap(tree[root<<1].lsum1,tree[root<<1].lsum2);
    swap(tree[root<<1].rsum1,tree[root<<1].rsum2);

    swap(tree[root<<1|1].sum1,tree[root<<1|1].sum2);
    swap(tree[root<<1|1].lsum1,tree[root<<1|1].lsum2);
    swap(tree[root<<1|1].rsum1,tree[root<<1|1].rsum2);

    tree[root].lazy  = 0;//当前的lazy标记需要取消
}

//向上更新
void pushup(int root)
{
    tree[root].lsum1=tree[root<<1].lsum1;
    tree[root].lsum2=tree[root<<1].lsum2;
    if(tree[root<<1].lsum1==tree[root<<1].len) tree[root].lsum1+=tree[root<<1|1].lsum1;//当最左边连续1的个数等于长度的时候
    if(tree[root<<1].lsum2==tree[root<<1].len) tree[root].lsum2+=tree[root<<1|1].lsum2;

    tree[root].rsum1=tree[root<<1|1].rsum1;
    tree[root].rsum2=tree[root<<1|1].rsum2;
    if(tree[root<<1|1].rsum1==tree[root<<1|1].len) tree[root].rsum1+=tree[root<<1].rsum1;
    if(tree[root<<1|1].rsum2==tree[root<<1|1].len) tree[root].rsum2+=tree[root<<1].rsum2;

    tree[root].sum1 = max(max(tree[root<<1].sum1, tree[root<<1|1].sum1), tree[root<<1].rsum1+tree[root<<1|1].lsum1);
    tree[root].sum2 = max(max(tree[root<<1].sum2, tree[root<<1|1].sum2), tree[root<<1].rsum2+tree[root<<1|1].lsum2);
}

void build_tree(int l,int r,int root)
{
    tree[root].l = l,tree[root].r = r,tree[root].lazy = 0;
    tree[root].len = r - l + 1;
    if(l == r)
    {
        int x;
        scanf("%d",&x);
        if(x)
        {
            tree[root].lsum1 = tree[root].rsum1 = tree[root].sum1 = 1;
            tree[root].lsum2 = tree[root].rsum2 = tree[root].sum2 = 0;
        }
        else
        {
            tree[root].lsum1 = tree[root].rsum1 = tree[root].sum1 = 0;
            tree[root].lsum2 = tree[root].rsum2 = tree[root].sum2 = 1;
        }
        return ;
    }

    int mid = (l + r) >> 1;
    build_tree(l,mid,root<<1);
    build_tree(mid+1,r,root<<1|1);
    pushup(root);//记得向上更新
}

void change(int a,int b,int l,int r,int root)
{
    if(a <= l && b >= r)
    {
        swap(tree[root].sum1,tree[root].sum2);
        swap(tree[root].lsum1,tree[root].lsum2);
        swap(tree[root].rsum1,tree[root].rsum2);
        tree[root].lazy ^= 1;//lazy标记
        return ;
    }
    int mid = (l + r) / 2;
    pushdown(root);//标记下移
    if(b <= mid)
        change(a,b,l,mid,root<<1);
    else if(a > mid)
        change(a,b,mid+1,r,root<<1|1);
    else
    {
        change(a,mid,l,mid,root<<1);
        change(mid+1,b,mid+1,r,root<<1|1);
    }
    pushup(root);
}

int query(int a,int b,int l,int r,int root)
{
    if(a <= l && b >= r)
        return tree[root].sum1;

    int mid = (l + r)/2;
    pushdown(root);
    if(b <= mid)
        return query(a,b,l,mid,root<<1);
    else if(a > mid)
        return query(a,b,mid+1,r,root<<1|1);
    else
    {
        //看起来很多 就是左子树的最大值和右子树的最大值和两个子树合并起来的值取最大的一个
        int m1 = query(a,mid,l,mid,root<<1);
        int m2 = query(mid+1,b,mid+1,r,root<<1|1);
        int m3 = max(m1,m2);
        int m4 = min(tree[root<<1].rsum1,mid-a+1);
        int m5 = min(b-mid,tree[root<<1|1].lsum1);
        return max(m3,m4+m5);
    }
    pushup(root);
}

int main()
{
    while(scanf("%d",&n)!=EOF)
    {
        build_tree(1,n,1);
        scanf("%d",&m);
        while(m--)
        {
            int k,a,b;
            scanf("%d%d%d",&k,&a,&b);
            if(k)
                change(a,b,1,n,1);
            else
            {
                int ans = query(a,b,1,n,1);
                printf("%d\n",ans);
            }
        }
    }
    return 0;
}

你可能感兴趣的:(数据结构-线段树)