P2572 [SCOI2010]序列操作
Posted Jozky86
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了P2572 [SCOI2010]序列操作相关的知识,希望对你有一定的参考价值。
题意:
一个长度为n的01序列,下标从0开始,现在有五种变换操作和询问操作:
- 0 l r 把[l,r]区间内的所有数全变成0
- 1 l r 把[l,r]区间内的所有数全变成1
- 2 l r把[l,]区间内所有数全部取反
- 3 l r询问[l,r]区间内总共有多少个1
- 4 l r询问[l,r]区间内最多有多少连续的1
题解:
参考题解
很明显,裸的线段树,但是很难写。。
线段树要维护以下信息:
- sum:区间内1的个数
- maxx[0/1]区间内0/1最长连续子段
- lmax[0,1]包含区间左端点最长0/1子段
- rmax[0,1]包含区间右端点最长0/1子段
还有区间操作,所以还需要标记
- lazy=-1,0,1 .-1表示为无状态,0/1表示区间全部赋值为0/1
- rev=0,1,表示区间是否翻转
sum是维护区间内1的数量,maxx,lmax,rmax是用来维护区间内最长的01段(我们需要同时维护0段和1段,因为题目中存在反转操作)
需要维护的元素比较多,我们从pushup开始
区间和直接相加
然后维护0/1段
包含左端点的连续子段有两种情况:
- 当左区间全满/全空时,左端点可以跨越与右区间相连
- 直接继承左区间的lmax
右区间更新同理
对于区间最长子段,有三种情况:
- 直接继承左区间的较大值
- 直接继承右区间的较大值
- 左区间含右端点的最长子段+右区间含左端点的最长子段(即最长部分跨越区间分割线)
然后就是pushdown
线段树pushdown中,需要明确两个事:
- 标记的优先级
- 下放某个标记是否会对子节点的其他类型标记有所影响
在拆解一个标记时, 我们不仅需要明确将此标记下放到子节点, 同类型的标记应该如何改变, 而更应明确拆解此标记会对 不同类型的标记有何种影响
我们有两个标记,分别表示反转操作与重置操作。区间全体赋值优先级肯定高于翻转。如果先反转再重置,反转就没用了。所以当我们执行重置操作时,应该把反转标记清空。同时在下放标记时,应先下放重置标记,再下放反转标记,否则反转就会被重置覆盖。
在区间赋值标记拆解时,需要将子区间赋值标记更新为此值,并将子节点反转标记清空
在区间反转标记拆解时,需要分两种情况考虑此标记对子区间区间赋值和翻转标记造成的影响。因为赋值标记优先级大于反转标记,所以有赋值标记时,直接区间赋值标记取反,其余情况翻转标记异或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]序列操作的主要内容,如果未能解决你的问题,请参考以下文章