bzoj1858 [Scoi2010]序列操作——线段树

题目:https://www.lydsy.com/JudgeOnline/problem.php?id=1858

线段树...调了一个上午...(后面带 // 的都是改出来的)

lazy 标记的下放好麻烦,还得考虑赋值和取反的先后顺序什么的...

因为在取反时把赋值标记 swap 了,所以下放的时候先判断取反再判断赋值...

而且WA了一上午的原因竟然是一开始不慎把取反以为成翻转了,后来没改干净...那个 rev 的名字啊...

总之没有太改变自己最初的想法、改了些细节就A了还是很高兴的!

代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int const maxn=1e5+5;
int n,m,op,a,b,c[maxn];
struct N{
    int sum,z[3],y[3],m[3];
    int lz[3],rev,len;
}t[maxn<<2];
int rd()
{
    int ret=0;char ch=getchar();
    while(ch<‘0‘||ch>‘9‘)ch=getchar();
    while(ch>=‘0‘&&ch<=‘9‘)ret=ret*10+ch-‘0‘,ch=getchar();
    return ret;
}
void pushup(int x)
{
    int ls=(x<<1),rs=(x<<1|1);
    t[x].sum=t[ls].sum+t[rs].sum;
    for(int i=0;i<=1;i++)
    {
        t[x].z[i]=t[ls].z[i]+(t[ls].z[i]==t[ls].len?t[rs].z[i]:0);
        t[x].y[i]=t[rs].y[i]+(t[rs].y[i]==t[rs].len?t[ls].y[i]:0);
//        t[x].m[i]=max(max(t[x].z[i],t[x].y[i]),t[ls].y[i]+t[rs].z[i]);//
        t[x].m[i]=max(max(t[ls].m[i],t[rs].m[i]),t[ls].y[i]+t[rs].z[i]);//
    }
}
void upt(int x,int val)//赋值
{
    t[x].lz[val]=1;
    t[x].lz[!val]=0; t[x].rev=0;//!
    t[x].sum=t[x].len*val;
    t[x].z[val]=t[x].y[val]=t[x].m[val]=t[x].len;
    t[x].z[!val]=t[x].y[!val]=t[x].m[!val]=0;
}
void re(int x)//取反
{
    swap(t[x].z[0],t[x].z[1]); swap(t[x].y[1],t[x].y[0]);//!!!
    swap(t[x].m[1],t[x].m[0]);
    t[x].sum=t[x].len-t[x].sum; t[x].rev^=1;
    swap(t[x].lz[1],t[x].lz[0]);//!
}
void pushdown(int x)
{
//    if(t[x].len==1)return;//
    int ls=(x<<1),rs=(x<<1|1);
    if(t[x].rev)t[x].rev^=1,re(ls),re(rs);
    for(int v=0;v<=1;v++)
        if(t[x].lz[v])t[x].lz[v]=0,upt(ls,v),upt(rs,v);//顺序
}
void build(int x,int l,int r)
{
    t[x].len=r-l+1;
    if(l==r)
    {
        t[x].z[c[l]]=t[x].y[c[l]]=t[x].m[c[l]]=1;
        t[x].sum=c[l]; //
        return;
    }
    int mid=((l+r)>>1);
    build(x<<1,l,mid); build(x<<1|1,mid+1,r);
    pushup(x);
}
void update(int x,int l,int r,int L,int R,int val)
{
    if(l>=L&&r<=R)
    {
        upt(x,val);return;
    }
    pushdown(x);
    int mid=((l+r)>>1);
    if(mid>=L)update(x<<1,l,mid,L,R,val);
    if(mid<R)update(x<<1|1,mid+1,r,L,R,val);
    pushup(x);
}
void rever(int x,int l,int r,int L,int R)
{
    if(l>=L&&r<=R)
    {
        re(x);return;
    }
    int mid=((l+r)>>1);
    pushdown(x);
    if(mid>=L)rever(x<<1,l,mid,L,R);
    if(mid<R)rever(x<<1|1,mid+1,r,L,R);
    pushup(x);
}
int query(int x,int l,int r,int L,int R)
{
    if(l>=L&&r<=R)return t[x].sum;
    int mid=((l+r)>>1),ret=0;
    pushdown(x);
    if(mid>=L)ret+=query(x<<1,l,mid,L,R);
    if(mid<R)ret+=query(x<<1|1,mid+1,r,L,R);
    return ret;
}
int ask(int x,int l,int r,int L,int R)
{
    if(l>=L&&r<=R)return t[x].m[1];
    pushdown(x);//
    int mid=((l+r)>>1);
    if(mid>=R)return ask(x<<1,l,mid,L,R);
    if(mid<L)return ask(x<<1|1,mid+1,r,L,R);
    int ret=0;
    ret=max(ask(x<<1,l,mid,L,R),ask(x<<1|1,mid+1,r,L,R));
    ret=max(ret,min(t[x<<1].y[1],mid-L+1)+min(t[x<<1|1].z[1],R-mid));
    return ret;
}
int main()
{
    n=rd();m=rd();
    for(int i=1;i<=n;i++)c[i]=rd();
    build(1,1,n);
    while(m--)
    {
        op=rd(); a=rd()+1; b=rd()+1;
        if(op==0||op==1)update(1,1,n,a,b,op);
        if(op==2)rever(1,1,n,a,b);
        if(op==3)printf("%d\n",query(1,1,n,a,b));
        if(op==4)printf("%d\n",ask(1,1,n,a,b));
    }
    return 0;
} 

原文地址:https://www.cnblogs.com/Zinn/p/9186579.html

时间: 2024-10-10 03:21:53

bzoj1858 [Scoi2010]序列操作——线段树的相关文章

bzoj1858: [Scoi2010]序列操作 线段树

线段树,维护七个值两个标记. 用结构体快了一倍…… #include<bits/stdc++.h> #define N (1<<18) #define M (l+r>>1) #define P (k<<1) #define S (k<<1|1) #define K l,r,k #define L l,M,P #define R M+1,r,S #define Z int l=0,int r=n-1,int k=1 using namespace

【BZOJ-1858】序列操作 线段树

1858: [Scoi2010]序列操作 Time Limit: 10 Sec  Memory Limit: 64 MBSubmit: 1961  Solved: 991[Submit][Status][Discuss] Description lxhgww最近收到了一个01序列,序列里面包含了n个数,这些数要么是0,要么是1,现在对于这个序列有五种变换操作和询问操作: 0 a b 把[a, b]区间内的所有数全变成0 1 a b 把[a, b]区间内的所有数全变成1 2 a b 把[a,b]区

BZOJ 1858 SCOI2010 序列操作 线段树

题目大意:给定一个01序列,提供三种操作: 0:把一段区间的全部元素都变成0 1:把一段区间的全部元素都变成1 2:把一段区间内的全部元素全都取反 3:查询一段区间内1的个数 4:查询一段区间内最长的一段连续的1 首先假设没有操作4这就是bitset的水题... 多了这个,我们考虑线段树 线段树的每个节点存改动标记和翻转标记,以及该区间的信息 尽管查询的信息都是1 可是我们要连0一起保存 由于翻转后0就变成了1 1就变成了0 区间信息包含: 左端点的元素 右端点的元素 左端点開始的最长的连续元素

bzoj 2962 序列操作(线段树)

题外话 做这道题我整个人都非常的绝望,推了一会发现是线段树裸题,然后调了N久一直是WA 情况是这样的 开始WA的几百毫秒的都是由于我比较SB造成的,可是跑了10几秒的程序我查了N久也查不出错 最后灵机一动把50000改成60000就过了,也不知道为啥T_T Description 一个长度为n的序列,有3种操作 1:区间加c 2:区间取为相反数 3:询问区间中选择c个数相乘的所有方案的和mod19940417的值 Solution 这个操作3非常鬼畜,似乎没啥好的办法,但是仔细推导一番会发现这个

bzoj 1858 序列操作(线段树)

题外话 本来想练练线段树的,然后发现这题及其蛋疼,要打一坨标记,这是我写过的最长的线段树了= = 然后我很SB的把R打成了r调了一个下午真是蛋疼QvQ Description: 给定一个0/1序列,有如下5个操作: 0:区间赋值为0 1:区间赋值为1 2:区间取反 3:询问区间内1的个数 4:询问区间内最大连续1的个数 Solution 没有操作4这显然就是个SB题,有了操作4我们需要打几个标记 我们需要从左端点开始连续0/1的个数,从右端点开始的连续0/1个数 区间内0/1个数,区间内最大连续

【分块】bzoj1858 [Scoi2010]序列操作

分块 Or 线段树 分块的登峰造极之题 每块维护8个值: 包括左端点在内的最长1段: 包括右端点在内的最长1段: 该块内的最长1段: 该块内1的个数: 包括左端点在内的最长0段://这四个是因为可能有翻转操作,需要swap 0有关的标记 和 1有关的标记 包括右端点在内的最长0段: 该块内的最长0段: 该块内0的个数. 2个懒标记:是否翻转,覆盖成了什么. 怎么处理一个块上有两个标记的情况呢? 若该块原来没有任何标记,或要打的标记和原本的标记种类相同,则直接打上标记: 若已有翻转标记,再覆盖时则

bzoj1858: [Scoi2010]序列操作

lazy-tag线段树. #include<cstdio> #include<algorithm> #include<cstring> using namespace std; const int maxn = 800000 + 10; struct Segtree { #define lc(x) ((x)<<1) #define rc(x) (((x)<<1)|1) int l[maxn],r[maxn]; int l1[maxn],r1[ma

BZOJ1858[Scoi2010]序列操作 题解

题目大意: 有一个01序列,现在对于这个序列有五种变换操作和询问操作: 0 a b 把[a, b]区间内的所有数全变成0:1 a b 把[a, b]区间内的所有数全变成1:2 a b 把[a,b]区间内的所有数全部取反,也就是说把所有的0变成1,把所有的1变成0:3 a b 询问[a, b]区间内总共有多少个1:4 a b 询问[a, b]区间内最多有多少个连续的1. 思路: 维护每一段数的和.左端和右端以及整段中连续的0和1的长度,并使用标记进行下传. 代码: 1 #include<cstdi

bzoj 2962 序列操作——线段树(卷积?)

题目:https://www.lydsy.com/JudgeOnline/problem.php?id=2962 如果 _,_,_,-- 变成了 (_+k),(_+k),(_+k),-- ,计算就是在每个括号里选 _ 或 k ,乘起来求和. 为了算那个,枚举选了 j 个 k :剩下那部分的乘积就是sm[cr][ i-j ]!j 和 k 可以在 len 里除了那 i-j 个位置里选,所以乘上 k^j 再乘上 C( len-(i-j) , j ) . 调了2h+竟然只因组合数推导公式写错-- #in