P2572 [SCOI2010]序列操作

Posted Jozky86

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了P2572 [SCOI2010]序列操作相关的知识,希望对你有一定的参考价值。

P2572 [SCOI2010]序列操作

题意:

一个长度为n的01序列,下标从0开始,现在有五种变换操作和询问操作:

  1. 0 l r 把[l,r]区间内的所有数全变成0
  2. 1 l r 把[l,r]区间内的所有数全变成1
  3. 2 l r把[l,]区间内所有数全部取反
  4. 3 l r询问[l,r]区间内总共有多少个1
  5. 4 l r询问[l,r]区间内最多有多少连续的1

题解:

参考题解
很明显,裸的线段树,但是很难写。。
线段树要维护以下信息:

  1. sum:区间内1的个数
  2. maxx[0/1]区间内0/1最长连续子段
  3. lmax[0,1]包含区间左端点最长0/1子段
  4. rmax[0,1]包含区间右端点最长0/1子段

还有区间操作,所以还需要标记

  1. lazy=-1,0,1 .-1表示为无状态,0/1表示区间全部赋值为0/1
  2. rev=0,1,表示区间是否翻转

sum是维护区间内1的数量,maxx,lmax,rmax是用来维护区间内最长的01段(我们需要同时维护0段和1段,因为题目中存在反转操作)


需要维护的元素比较多,我们从pushup开始
区间和直接相加
然后维护0/1段
包含左端点的连续子段有两种情况:

  1. 当左区间全满/全空时,左端点可以跨越与右区间相连
  2. 直接继承左区间的lmax

右区间更新同理

对于区间最长子段,有三种情况:

  1. 直接继承左区间的较大值
  2. 直接继承右区间的较大值
  3. 左区间含右端点的最长子段+右区间含左端点的最长子段(即最长部分跨越区间分割线)

然后就是pushdown
线段树pushdown中,需要明确两个事:

  1. 标记的优先级
  2. 下放某个标记是否会对子节点的其他类型标记有所影响

在拆解一个标记时, 我们不仅需要明确将此标记下放到子节点, 同类型的标记应该如何改变, 而更应明确拆解此标记会对 不同类型的标记有何种影响

我们有两个标记,分别表示反转操作与重置操作。区间全体赋值优先级肯定高于翻转。如果先反转再重置,反转就没用了。所以当我们执行重置操作时,应该把反转标记清空。同时在下放标记时,应先下放重置标记,再下放反转标记,否则反转就会被重置覆盖。

在区间赋值标记拆解时,需要将子区间赋值标记更新为此值,并将子节点反转标记清空

在区间反转标记拆解时,需要分两种情况考虑此标记对子区间区间赋值翻转标记造成的影响。因为赋值标记优先级大于反转标记,所以有赋值标记时,直接区间赋值标记取反,其余情况翻转标记异或1


通过这个题会对线段树的lazy以及操作有更深的理解

代码:

// Problem: P2572 [SCOI2010]序列操作
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P2572
// Memory Limit: 125 MB
// Time Limit: 1000 ms
// Data:2021-08-11 12:46:07
// By Jozky

#include <bits/stdc++.h>
#include <unordered_map>
#define debug(a, b) printf("%s = %d\\n", a, b);
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> PII;
clock_t staidTime, endTime;
//Fe~Jozky
const ll INF_ll= 1e18;
const int INF_int= 0x3f3f3f3f;
template <typename T> inline void read(T& x)
{
    T f= 1;
    x= 0;
    char ch= getchar();
    while (0 == isdigit(ch)) {
        if (ch == '-')
            f= -1;
        ch= getchar();
    }
    while (0 != isdigit(ch))
        x= (x << 1) + (x << 3) + ch - '0', ch= getchar();
    x*= f;
}
template <typename T> inline void write(T x)
{
    if (x < 0) {
        x= ~(x - 1);
        putchar('-');
    }
    if (x > 9)
        write(x / 10);
    putchar(x % 10 + '0');
}
void rd_test()
{
#ifdef ONLINE_JUDGE
#else
    staidTime= clock();
    freopen("in.txt", "r", stdin);
#endif
}
void Time_test()
{
#ifdef ONLINE_JUDGE
#else
    endTime= clock();
    printf("\\nRun Time:%lfs\\n", (double)(endTime - staidTime) / CLOCKS_PER_SEC);
#endif
}

const int maxn= 3e5 + 9;
struct seg
{
    int l, r;
    int sum;
    int lazy;
    int rev;
    int maxx[2], lmax[2], rmax[2];
} tr[maxn << 2];
int a[maxn];
int n, m;
#define lid (id << 1)
#define rid (id << 1) | 1
void pushup(seg& ret, seg& L, seg& R)
{
    ret.sum= L.sum + R.sum;
    for (int i= 0; i <= 1; i++) {
        ret.lmax[i]= L.lmax[i];
        if (i == 1 && L.sum == L.r - L.l + 1) //左区间全满
            ret.lmax[i]+= R.lmax[i]; //可以跨越
        else if (i == 0 && L.sum == 0) //左区间全空,说明可以跨越到右区间
            ret.lmax[i]+= R.lmax[i];

        ret.rmax[i]= R.rmax[i];
        if (i == 1 && R.sum == R.r - R.l + 1) //如果右区间满,可以跨越到左区间
            ret.rmax[i]+= L.rmax[i];
        else if (i == 0 && R.sum == 0)
            ret.rmax[i]+= L.rmax[i];
        //左子树的右侧和右子树的左侧连接起来
        ret.maxx[i]= L.rmax[i] + R.lmax[i]; //中间
        //继承左子区间
        ret.maxx[i]= max(ret.maxx[i], L.maxx[i]); //继承子区间
        //继承右子区间
        ret.maxx[i]= max(ret.maxx[i], R.maxx[i]);
    }
}
void build(int id, int l, int r)
{
    tr[id].l= l;
    tr[id].r= r;
    tr[id].lazy= -1; //无状态
    if (l == r) {
        tr[id].sum= a[l];
        tr[id].maxx[0]= tr[id].lmax[0]= tr[id].rmax[0]= (a[l] == 0);
        tr[id].maxx[1]= tr[id].lmax[1]= tr[id].rmax[1]= (a[l] == 1);
        return;
    }
    int mid= (l + r) >> 1;
    build(lid, l, mid);
    build(rid, mid + 1, r);
    pushup(tr[id], tr[lid], tr[rid]);
}
void solve(int id, int val)
{
    tr[id].sum= (tr[id].r - tr[id].l + 1) * val;
    tr[id].lazy= val;
    tr[id].rev= 0;
    tr[id].maxx[val]= tr[id].lmax[val]= tr[id].rmax[val]= (tr[id].r - tr[id].l + 1);
    tr[id].maxx[val ^ 1]= tr[id].lmax[val ^ 1]= tr[id].rmax[val ^ 1]= 0;
}
void solve2(int id)
{
    //因为反转,所以取剩余部分
    tr[id].sum= (tr[id].r - tr[id].l + 1) - tr[id].sum;
    //总和考虑优先级,对其他标记的影响
    if (tr[id].lazy != -1)
        tr[id].lazy^= 1;
    else
        tr[id].rev^= 1;
    //因为反转,所以区间0/1子段情况也要反转
    swap(tr[id].maxx[0], tr[id].maxx[1]);
    swap(tr[id].lmax[0], tr[id].lmax[1]);
    swap(tr[id].rmax[0], tr[id].rmax[1]);
}
void pushdown(int id)
{
    if (tr[id].lazy != -1) {
        tr[id].rev= 0; //清空反转标记
        solve(lid, tr[id].lazy);
        solve(rid, tr[id].lazy);
        tr[id].lazy= -1;
    }
    if (tr[id].rev) {
        solve2(lid);
        solve2(rid);
        tr[id].rev= 0;
    }
}
void update(int id, int val, int l, int r)
{
    if (tr[id].l == l && tr[id].r == r) {
        if (val == 0 || val == 1) {
            solve(id, val);
        }
        else if (val == 2) {
            solve2(id);
        }
        return;
    }
    pushdown(id);
    int mid= (tr[id].l + tr[id].r) >> 1;
    if (mid < l)
        update(rid, val, l, r);
    else if (mid >= r)
        update(lid, val, l, r);
    else
        update(lid, val, l, mid), update(rid, val, mid + 1, r);
    pushup(tr[id], tr[lid], tr[rid]);
}
int query(int id, int l, int r)
{
    if (tr[id].l == l && tr[id].r == r)
        return tr[id].sum;
    pushdown(id);
    int mid= (tr[id].l + tr[id].r) >> 1;
    if (mid < l)
        return query(rid, l, r);
    else if (mid >= r)
        return query(lid, l, r);
    else
        return query(lid, l, mid) + query(rid, mid + 1, r);
}
seg Q_max(int id, int l, int r)
{
    if (tr[id].l == l && tr[id].r == r)
        return tr[id];
    pushdown(id);
    int mid= (tr[id].l + tr[id].r) >> 1;

    if (mid < l)
        return Q_max(rid, l, r);
    else if (mid >= r)
        return Q_max(lid, l, r);
    else {
        seg ret;
        seg L= Q_max(lid, l, mid), R= Q_max(rid, mid + 1, r);
        pushup(ret, L, R);
        return ret;
    }
}
int main()
{
    //rd_test();
    read(n), read(m);
    for (int i= 1; i <= n; i++)
        read(a[i]);
    build(1, 1, n);
    while (m--) {
        int op, l, r;
        read(op), read(l), read(r);
        l++, r++;
        if (op == 0)
            update(1, 0, l, r); //区间全变成0
        else if (op == 1)
            update(1, 1, l, r); //区间全变成1
        else if (op == 2)
            update(1, 2, l, r); //区间全部取反
        else if (op == 3)
            printf("%d\\n", query(1, l, r)); //询问区间内有多少个1
        else if (op == 4)
            printf("%d\\n", Q_max(1, l, r).maxx[1]); //区间内有多少个连续的1
    }
    //Time_test();
    return 0;
}

以上是关于P2572 [SCOI2010]序列操作的主要内容,如果未能解决你的问题,请参考以下文章

P2572 [SCOI2010]序列操作

题解Luogu P2572 [SCOI2010]序列操作

bzoj 1858: [Scoi2010]序列操作 || 洛谷 P2572

解题报告:luogu P2572

解题报告:luogu P2572

AC日记——[Scoi2010]序列操作 bzoj 1858