您的位置:首页 > 其它

【SCOI2010】序列操作(求和+连续最长线段树)

2018-01-25 13:52 316 查看
临近期末,说什么竞赛辅导全都停了,一切为期末考试让路,我反正已经考不好了,我还是像往常一样来机房,同级的大佬们都在教室学习,我一人不知道刷些什么,就熟练地点开了——

Created with Raphaël 2.1.2洛谷打卡题目线段树按难度排序找到(浅)蓝题我想做吗?开始写代码yesno

没错我只是来玩Markdown的,我就找到了如下题目,是道紫题,标签有且只有线段树。本着挑战的精神,我就做了两天…对于我这种蒟蒻来说,显然是借鉴了题解的部分思路。

题目描述

顾佬最近收到了一个01序列,序列里面包含了n个数,这些数要么是0,要么是1,现在对于这个序列有五种变换操作和询问操作:

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

对于每一种询问操作,顾佬都需要给出回答,聪明的程序员们,你们能帮助他吗?

输入输出格式

输入格式:

输入数据第一行包括2个数,n和m,分别表示序列的长度和操作数目

第二行包括n个数,表示序列的初始状态

接下来m行,每行3个数,op, a, b,(0<=op<=4,0<=a<=b< n)表示对于区间[a, b]执行标号为op的操作

输出格式:

对于每一个询问操作,输出一行,包括1个数,表示其对应的答案

首先这道题的难点就是如何维护和查询区间内最长连续1的个数,这部分许多类似题目都有。

要分情况,向上合并后区间的最长连续1可能等于:

1. 区间的连续最长1;

2. 区间的连续最长1;

3. 左右区间的中间部分;

灵性的地方来了,第3种情况如何实现呢,不难想到可以维护每个区间左起1和右起1的个数,然后三种情况用max括起来。

然后这两个新信息又如何向上合并呢?思考一下,对于左起,情况如下:

1. 若左子树全部为1,根的左起1就是左区间长度加上右字树的左起1

2. 否则就是左子树的左起1

右起同理。

这样一来就要维护三个信息了,再看看修改操作中,需要反转…那么再分别维护这三个信息的0值版本,翻转时进行swap。

此处要注意覆盖操作的优先级高于翻转,也就是说先下放覆盖标签。

嗯,有够麻烦。

但是查询起来也不是那么简单的,因为最终是一个区间内的连续最长1,所以查询函数每次返回一个节点,然后分情况讨论,向上递归。那么依然对于左起:

1. 如果左子树为空,当前节点左起1为其右子树左起1

2. 如果左子树不空:

(1)若左子树左起1等于左子树区间长度,当前节点左起1为左子树区间长加右子树左起1

(2)否则当前节点左起1为其左子树左起1

右起同理,最终区间最长1同向上合并操作。

最后返回查询区间的最终最长连续1即可

求和什么的就不必多说,常规操作就好。

代码如下:

#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=100002;
int a[maxn],n,m,i,x,y,s;
struct node{int l,r,t;}ha;
struct segtree{
#define lson (o<<1)
#define rson (o<<1|1)
#define mid ((l+r)>>1)
int sumv[maxn<<2],suml[maxn<<2],sumr[maxn<<2],sumt[maxn<<2];//v为value,l为left,r为right,t为total
int sumlz[maxn<<2],sumrz[maxn<<2],sumtz[maxn<<2];//上一行信息的0值版本,翻转时用
int reiv[maxn<<2],itiv[maxn<<2],xorv[maxn<<2];//三个标记,这鸟语的0和1就不要在意了
inline void pushup(int o,int l,int r){//向上合并
if(suml[lson]==mid-l+1)suml[o]=suml[lson]+suml[rson];//第1种情况
else suml[o]=suml[lson];//第2种情况
if(sumr[rson]==r-mid)sumr[o]=sumr[rson]+sumr[lson];
else sumr[o]=sumr[rson];//右起同理
sumt[o]=max(max(sumt[rson],sumt[lson]),suml[rson]+sumr[lson]);//实际最长1的三种情况
if(sumlz[lson]==mid-l+1)sumlz[o]=sumlz[lson]+sumlz[rson];
else sumlz[o]=sumlz[lson];
if(sumrz[rson]==r-mid)sumrz[o]=sumrz[rson]+sumrz[lson];
else sumrz[o]=sumrz[rson];
sumtz[o]=max(max(sumtz[rson],sumtz[lson]),sumlz[rson]+sumrz[lson]);//0值版本同理
sumv[o]=sumv[lson]+sumv[rson];
}
inline void pushdown(int o,int l,int r){//下放
if(itiv[o]){
sumv[lson]=suml[lson]=sumr[lson]=sumt[lson]=mid-l+1;
sumv[rson]=suml[rson]=sumr[rson]=sumt[rson]=r-mid;
sumlz[lson]=sumlz[rson]=sumrz[lson]=sumrz[rson]=sumtz[lson]=sumtz[rson]=0;
itiv[rson]=itiv[lson]=1;
xorv[lson]=xorv[rson]=reiv[lson]=reiv[rson]=0;
itiv[o]=0;
}else if(reiv[o]){
sumlz[lson]=sumrz[lson]=sumtz[lson]=mid-l+1;
sumlz[rson]=sumrz[rson]=sumtz[rson]=r-mid;
sumv[lson]=sumv[rson]=suml[lson]=suml[rson]=sumr[lson]=sumr[rson]=sumt[lson]=sumt[rson]=0;
reiv[rson]=reiv[lson]=1;
xorv[lson]=xorv[rson]=itiv[lson]=itiv[rson]=0;
reiv[o]=0;
}
//注意并列的标签不要加else,一并下推
if(xorv[o]){
sumv[lson]=mid-l+1-sumv[lson],sumv[rson]=r-mid-sumv[rson];
swap(sumt[lson],sumtz[lson]);swap(sumt[rson],sumtz[rson]);
swap(suml[lson],sumlz[lson]);swap(suml[rson],sumlz[rson]);
swap(sumr[lson],sumrz[lson]);swap(sumr[rson],sumrz[rson]);
xorv[lson]^=1;xorv[rson]^=1;
xorv[o]=0;
}
}
inline void build(int o,int l,int r){建树
reiv[o]=itiv[o]=xorv[o]=0;
if(l==r){
suml[o]=sumr[o]=sumt[o]=sumv[o]=a[l];
sumlz[o]=sumrz[o]=sumtz[o]=a[l]^1;
return;
}
build(lson,l,mid);
build(rson,mid+1,r);
pushup(o,l,r);
}
inline int querysum(int o,int l,int r,int ql,int qr){//求和,即1的个数
if(ql<=l&&qr>=r)return sumv[o];
pushdown(o,l,r);
int ans=0;
if(ql<=mid)ans+=querysum(lson,l,mid,ql,qr);
if(qr>mid)ans+=querysum(rson,mid+1,r,ql,qr);
return ans;
}
inline node querymax(int o,int l,int r,int ql,int qr){//求最长连续1
if(ql<=l&&qr>=r)return (node){suml[o],sumr[o],sumt[o]};
pushdown(o,l,r);
node t,tl,tr;//用结构体分别维护三个信息
int fll=0,flr=0;//判断子树是否为空的flag
if(ql<=mid)tl=querymax(lson,l,mid,ql,qr);else{tl=(node){0,0,0};fll=1;}//赋成0,以免影响最终区间内最长连续1的比较
if(qr>mid)tr=querymax(rson,mid+1,r,ql,qr);else{tr=(node){0,0,0};flr=1;}
if(tl.l==mid-l+1)t.l=tl.l+tr.l;//1
else if(fll==1)t.l=tr.l;//2(1)
else t.l=tl.l;//2(2)
if(tr.r==r-mid)t.r=tr.r+tl.r;
else if(flr==1)t.r=tl.r;
else t.r=tr.r;
t.t=max(max(tr.t,tl.t),tl.r+tr.l);
return t;
}
inline void optiti(int o,int l,int r,int ql,int qr){//区间赋为1
if(ql<=l&&qr>=r){
sumv[o]=r-l+1;
sumlz[o]=sumrz[o]=sumtz[o]=0;
suml[o]=sumr[o]=sumt[o]=r-l+1;
xorv[o]=0,itiv[o]=1,reiv[o]=0;
return;
}
pushdown(o,l,r);
if(ql<=mid)optiti(lson,l,mid,ql,qr);
if(qr>mid)optiti(rson,mid+1,r,ql,qr);
pushup(o,l,r);
}
inline void optrei(int o,int l,int r,int ql,int qr){//区间赋为0
if(ql<=l&&qr>=r){
sumv[o]=0;
sumlz[o]=sumrz[o]=sumtz[o]=r-l+1;
suml[o]=sumr[o]=sumt[o]=0;
xorv[o]=0,itiv[o]=0,reiv[o]=1;
return;
}
pushdown(o,l,r);
if(ql<=mid)optrei(lson,l,mid,ql,qr);
if(qr>mid)optrei(rson,mid+1,r,ql,qr);
pushup(o,l,r);
}
inline void optxor(int o,int l,int r,int ql,int qr){//区间翻转
if(ql<=l&&qr>=r){
sumv[o]=r-l+1-sumv[o];
swap(sumt[o],sumtz[o]);
swap(suml[o],sumlz[o]);
swap(sumr[o],sumrz[o]);
xorv[o]^=1;
return;
}
pushdown(o,l,r);
if(ql<=mid)optxor(lson,l,mid,ql,qr);
if(qr>mid)optxor(rson,mid+1,r,ql,qr);
pushup(o,l,r);
}
}seg;
int main(){
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++)scanf("%d",&a[i]);
seg.build(1,1,n);
while(m--){
scanf("%d%d%d",&s,&x,&y);
x++;y++;
switch(s){//最好加上break
case 0:seg.optrei(1,1,n,x,y);break;
case 1:seg.optiti(1,1,n,x,y);break;
case 2:seg.optxor(1,1,n,x,y);break;
case 3:printf("%d\n",seg.querysum(1,1,n,x,y));break;
case 4:printf("%d\n",seg.querymax(1,1,n,x,y).t);break;
}
}
return 0;
}


顺便一说,这是我A掉的第一道紫题,恐怖如斯啊。

这一个学期马上要结束了,上半年初中时代的我最多只敢做黄题,上了高中简直开眼界。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  线段树 省选