[SCOI2010]序列操作(思维 + 线段树 + 模拟)

这篇博客深入探讨了一种处理区间查询与更新问题的数据结构——树状数组(线段树),特别是针对复杂操作的懒惰 Propagation 技术。博主详细分析了维护区间信息的需求,如区间和、最长连续子串等,并解释了如何在 pushup、pushdown 和更新过程中正确处理懒惰标记,包括它们之间的优先级和影响关系。最后,博主分享了代码实现和一些处理懒惰标记的技巧与注意事项,帮助读者更好地理解和应用这一技术。
摘要由CSDN通过智能技术生成

题目连接

参考题解1

参考题解2
及其复杂,等我全部研究完再写分析…
其他分析都对,错就错在 懒标记之间的相互影响 没分析好…

分析一下:

  1. 分析一下需要维护哪些信息:
    可以发现操作0 1 2都需要一个懒标记,add0,add1,re;
    再一步分析发现一个区间内add0和add1时不会同时出现的;
    所以这两个懒标记合在一起add(0表示add0,1表示add1,避免了后面过于复杂的讨论…)

    操作3需要维护一个区间中1的个数sum1,由于会翻转,所以维护一个sum0;
    对于操作4,我们借鉴acwing这个题:https://www.acwing.com/problem/content/246/
    很容易发现我们需要维护mx1(区间中最多有多少个连续的1) pre1(最长连续前缀1的个数) ne1(最长连续后缀1的个数);
    由于翻转,所以要维护mx0 pre0 ne0;

    总结:l,r; sum1,sum0; mx1,mx0; pre1,pre0; ne1,ne0; add,re;

  2. 如何维护信息?

(1)pushup时:(这里分析1的情况,0的情况也是如此)

		u.sum1 = l.sum1 + r.sum1;	//区间和
		u.mx1 = max(l.ne1 + r.pre1, max(l.mx1, r.mx1));		//l.ne1 + r.pre1 是中间拼接的部分
		u.pre1 = l.pre1; if(l.pre1 == (l.r - l.l + 1)) u.pre1 += r.pre1;
		u.ne1 = r.ne1; if(r.ne1 == (r.r - r.l + 1)) u.ne1 += l.ne1;
		//这个比较好理解,不说了...

(2)懒标记之间的影响 和 懒标记之间的优先级(不用刻意考虑懒标记的时间顺序问题!)

		1)懒标记之间的影响(假设当前tr[u]有re和add)
			此时传过来一个add',表示当前区间要全为0/1;
			那么我们可以发现,最终结果和之前的re,add都没用了,都会被当前这个add'所覆盖;
			即此时tr[u].re = 0; tr[u].add = add';
			此时传过来一个re',表示当前区间要翻转;
			那么我们可以发现,最终结果和我当前是否有add和re是有关的;
			此时我们可以保留add(或者也可以去分类讨论有无add...麻烦)
			此时如果有re,那么传过来re'相当于没有翻转;如果没有re,那么就翻转;
			即tr[u].re ^= 1;
			
		2)懒标记的优先级
			通过上面的分析,我们可以知道add的优先级是>re的!
			所以我们优先下传add,再下传re;
			验证:如果传过来一个re',如果我们保留add不变,此时有add和re'
				  那么下次下传的时候也会优先传add,是时间顺序上是正确的!

(3)pushdown时:(u 更新 lson rson)

		1)优先传add(这里讨论add=1的情况,0的情况相反而已的)
			首先lson,rson打上add标记;同时通过上面分析,lson rson的re标记置为0;
			lson,rson 的 sum1, mx1, pre1, ne1 全部变成区间长度;
						sum0, mx0, pre0, ne0 全部变成0;
			
		2)再传re
			通过上面的分析,lson,rson的add保留不变,re ^= 1;
			然后lson,rson的 sum1,sum0;交换   mx1,mx0;交换   pre1,pre0;交换   ne1,ne0;交换

(4)打懒标记时:

		这里我们update时就可以传一个参数p,表示当前的操作(要传的懒标记)
		然后分类讨论一下就行,和pushdown类似。

结束!!!

技巧 和 问题总结:

  1. 太复杂的题,pushup和pushdown还是单独写出来比较好
  2. 查询比较多的东西时,query直接返回node
  3. 懒标记比较多的,一定要想清楚懒标记优先级以及之间的影响关系;
  4. 懒标记太多的,修改时建议直接直接传懒标记,然后分类讨论一下
  5. 关于懒标记的顺序问题(注意不是优先级):
    我们每次进行一次操作,最多就传一个种类的懒标记;
    那么对于tr[u]来说,当前这个懒标记和之前已经打上去的懒标记的时间顺序是不同的;
    但是我们不用去人为规定顺序,这时候我们就要考虑懒标记之间的影响;
    也就是说,此时我们只需要考虑当前这个懒标记对之前已经打过的懒标记的影响即可;
  6. 关于判断懒标记优先级的问题:
    这个其实是要通过分析懒标记之间的影响关系分析出来的;
    也就是:先分析懒标记之间的影响关系,再分析懒标记优先级。
  7. build 和 打懒标记 时记得 return!!!

代码

int a[maxn];
int n,m;

struct node
{
	int l,r;
	
	int sum1,sum0;	//1,0的个数
	int mx1,mx0;	//最多有多少个0,1连续
	int pre1,pre0;	//最长1,0前缀
	int ne1,ne0;	//最长1,0后缀
	
	int add;	//区间覆盖懒标记(-1初始值,0是区间变0,1是区间变1)
	int re;		//反转懒标记
}tr[maxn*4];

void pushup(int u)
{
	tr[u].sum0 = tr[u<<1].sum0 + tr[u<<1|1].sum0;
	tr[u].sum1 = tr[u<<1].sum1 + tr[u<<1|1].sum1;
	
	tr[u].mx0 = max(tr[u<<1].ne0 + tr[u<<1|1].pre0, max(tr[u<<1].mx0, tr[u<<1|1].mx0));
	tr[u].mx1 = max(tr[u<<1].ne1 + tr[u<<1|1].pre1, max(tr[u<<1].mx1, tr[u<<1|1].mx1));

	tr[u].pre1 = tr[u<<1].pre1; if(tr[u<<1].pre1 == (tr[u<<1].r - tr[u<<1].l + 1)) tr[u].pre1 += tr[u<<1|1].pre1;
	tr[u].pre0 = tr[u<<1].pre0; if(tr[u<<1].pre0 == (tr[u<<1].r - tr[u<<1].l + 1)) tr[u].pre0 += tr[u<<1|1].pre0;

	tr[u].ne1 = tr[u<<1|1].ne1; if(tr[u<<1|1].ne1 == (tr[u<<1|1].r - tr[u<<1|1].l + 1)) tr[u].ne1 += tr[u<<1].ne1;
	tr[u].ne0 = tr[u<<1|1].ne0; if(tr[u<<1|1].ne0 == (tr[u<<1|1].r - tr[u<<1|1].l + 1)) tr[u].ne0 += tr[u<<1].ne0;
}

void pushdown(int u)	//下传懒标记
{
	if(tr[u].add != -1)	//优先传add
	{
		if(tr[u].add == 0)	//全覆盖为0
		{
			tr[u<<1].add = tr[u].add;
			tr[u<<1].re = 0;	//前面的re标记都没用了
			tr[u<<1].sum0 = tr[u<<1].mx0 = tr[u<<1].pre0 = tr[u<<1].ne0 = (tr[u<<1].r - tr[u<<1].l + 1);
			tr[u<<1].sum1 = tr[u<<1].mx1 = tr[u<<1].pre1 = tr[u<<1].ne1 = 0;
			
			tr[u<<1|1].add = tr[u].add;
			tr[u<<1|1].re = 0;	//前面的re标记都没用了
			tr[u<<1|1].sum0 = tr[u<<1|1].mx0 = tr[u<<1|1].pre0 = tr[u<<1|1].ne0 = (tr[u<<1|1].r - tr[u<<1|1].l + 1);
			tr[u<<1|1].sum1 = tr[u<<1|1].mx1 = tr[u<<1|1].pre1 = tr[u<<1|1].ne1 = 0;
		}
		else if(tr[u].add == 1)	//全覆盖为1
		{
			tr[u<<1].add = tr[u].add;
			tr[u<<1].re = 0;	//前面的re标记都没用了
			tr[u<<1].sum1 = tr[u<<1].mx1 = tr[u<<1].pre1 = tr[u<<1].ne1 = (tr[u<<1].r - tr[u<<1].l + 1);
			tr[u<<1].sum0 = tr[u<<1].mx0 = tr[u<<1].pre0 = tr[u<<1].ne0 = 0;
			
			tr[u<<1|1].add = tr[u].add;
			tr[u<<1|1].re = 0;	//前面的re标记都没用了
			tr[u<<1|1].sum1 = tr[u<<1|1].mx1 = tr[u<<1|1].pre1 = tr[u<<1|1].ne1 = (tr[u<<1|1].r - tr[u<<1|1].l + 1);
			tr[u<<1|1].sum0 = tr[u<<1|1].mx0 = tr[u<<1|1].pre0 = tr[u<<1|1].ne0 = 0;
		}
		
		tr[u].add = -1;
	}
	
	if(tr[u].re)	//再传翻转
	{
		tr[u<<1].re ^= 1;	//可能要改!!!
		swap(tr[u<<1].sum0,tr[u<<1].sum1);
		swap(tr[u<<1].mx0, tr[u<<1].mx1);
		swap(tr[u<<1].pre0,tr[u<<1].pre1);
		swap(tr[u<<1].ne0, tr[u<<1].ne1);
		
		tr[u<<1|1].re ^= 1;	//可能要改!!!
		swap(tr[u<<1|1].sum0,tr[u<<1|1].sum1);
		swap(tr[u<<1|1].mx0, tr[u<<1|1].mx1);
		swap(tr[u<<1|1].pre0,tr[u<<1|1].pre1);
		swap(tr[u<<1|1].ne0, tr[u<<1|1].ne1);
		
		tr[u].re = 0;
	}
}

void build(int u,int l,int r)
{
    tr[u] = {l,r};
	tr[u].add = -1;
    
	if(l == r)
	{
		if(a[l]) tr[u] = {l,r,1,0,1,0,1,0,1,0,-1,0};
		else tr[u] = {l,r,0,1,0,1,0,1,0,1,-1,0};
		return;
	}
	
	int mid = l + r >> 1;
	build(u<<1,l,mid);
	build(u<<1|1,mid+1,r);
	
	pushup(u);	
}

void update(int u,int l,int r,int p)
{
	if(l > r) return;
	
	if(tr[u].l >= l && tr[u].r <= r)	//打懒标记(分类讨论)
	{
		if(p == 0)		//区间变0
		{
			tr[u].sum0 = tr[u].mx0 = tr[u].pre0 = tr[u].ne0 = (tr[u].r - tr[u].l + 1);
			tr[u].sum1 = tr[u].mx1 = tr[u].pre1 = tr[u].ne1 = 0;
			
			tr[u].add = p;
			tr[u].re = 0;
		}
		else if(p == 1)	//区间变1
		{
			tr[u].sum1 = tr[u].mx1 = tr[u].pre1 = tr[u].ne1 = (tr[u].r - tr[u].l + 1);
			tr[u].sum0 = tr[u].mx0 = tr[u].pre0 = tr[u].ne0 = 0;
						
			tr[u].add = p;
			tr[u].re = 0;
		}
		else if(p == 2)	//区间翻转
		{
			swap(tr[u].sum0,tr[u].sum1);
			swap(tr[u].mx0, tr[u].mx1);
			swap(tr[u].pre0,tr[u].pre1);
			swap(tr[u].ne0, tr[u].ne1);
			
			tr[u].re ^= 1;	//可能要改!!!
		}
		return;
	}
	
	pushdown(u);
	
	int mid = tr[u].l + tr[u].r >> 1;
	if(l <= mid) update(u<<1, l, r, p);
	if(r > mid) update(u<<1|1, l, r, p);
	
	pushup(u);
}

void pushup1(node &u,node &l,node &r)
{
	u.sum0 = l.sum0 + r.sum0;
	u.sum1 = l.sum1 + r.sum1;
	
	u.mx0 = max(l.ne0 + r.pre0, max(l.mx0, r.mx0));
	u.mx1 = max(l.ne1 + r.pre1, max(l.mx1, r.mx1));

	u.pre1 = l.pre1; if(l.pre1 == (l.r - l.l + 1)) u.pre1 += r.pre1;
	u.pre0 = l.pre0; if(l.pre0 == (l.r - l.l + 1)) u.pre0 += r.pre0;

	u.ne1 = r.ne1; if(r.ne1 == (r.r - r.l + 1)) u.ne1 += l.ne1;
	u.ne0 = r.ne0; if(r.ne0 == (r.r - r.l + 1)) u.ne0 += l.ne0;
}

node query(int u,int l,int r)
{
	if(tr[u].l >= l && tr[u].r <= r) return tr[u];
	
	pushdown(u);
	
	int mid = tr[u].l + tr[u].r >> 1;
	
	if(r <= mid) return query(u<<1,l,r);
	else if(l > mid) return query(u<<1|1,l,r);
	else
	{
		node l_node = query(u<<1, l, r);
        node r_node = query(u<<1|1, l, r);

        //由两个子节点更新一个新的父节点
        node res;
        pushup1(res, l_node, r_node);    
        return res;
	}
}

int main()
{
	iire(n,m);
	for(int i=1;i<=n;i++) ire(a[i]);
	
	build(1,1,n);
	
	while(m--)
	{
		int op,l,r;
		ire(op);
		iire(l,r);
		
		l++; r++;
		
		if(op == 0) update(1,l,r,0);
		else if(op == 1) update(1,l,r,1);
		else if(op == 2) update(1,l,r,2);
		else if(op == 3) cout<<query(1,l,r).sum1<<endl;
		else cout<<query(1,l,r).mx1<<endl;
	}
	
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值