大致题意: 有(n)个点,规定(x,y)连通当且仅当(a_x=a_{x+1}=...=a_y=1)。给定零时刻(a_i)的值,每个时刻可能会发生两种事件:将(a_x)取反((0->1,1->0)),或询问(x,y)有多少个时刻连通。
树套树
这道题一眼树套树,然后随便推了推就推出来了,应该算是一道比较水的题目吧。
考虑用平面上一点((x,y))表示(x,y)的答案。
一个基本性质,如果(x,y)连通,则(x,y)之间的所有点都是连通的。
于是我们可以抠出序列中每一整段全是(1)的区间,然后类似于(ODT)用(set)进行维护,其中每个区间要维护它的诞生时间。
每次改变一个点的值的时候,无非就是连通若干区间或是断开一个区间。
无论是连通还是断开,都需要先计算原区间的贡献(用当前时间减去区间诞生时间)并从(set)中删去,然后往(set)中加入新的区间。
对于([l,r])区间,它的贡献范围就是左上角为((l,l))、右下角为((r,r))的一个矩形。
因此只要区间修改、单点查询,树状数组套线段树即可。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 300000
using namespace std;
int n,a[N+5];char s[N+5];
struct V//存储一个区间的信息
{
int l,r,t;I V(CI a=0,CI b=0,CI c=0):l(a),r(b),t(c){}//l,r为左右端点,t为诞生时间
I bool operator < (Con V& o) Con {return l<o.l;}
};set<V> S;typedef set<V>::iterator IT;//用set维护
class SegmentArray//树状数组套线段树
{
private:
int Rt[N+5];class SegmentTree//动态开点线段树
{
private:
int Nt;struct node {int V,S[2];}O[N*300];
public:
I void U(int& rt,CI L,CI R,CI v,CI l=1,CI r=n)//区间修改
{
if(!rt&&(rt=++Nt),L<=l&&r<=R) return (void)(O[rt].V+=v);RI mid=l+r>>1;
L<=mid&&(U(O[rt].S[0],L,R,v,l,mid),0),R>mid&&(U(O[rt].S[1],L,R,v,mid+1,r),0);
}
I int Q(int& rt,CI p,CI l=1,CI r=n)//单点查询
{
if(!rt||l==r) return O[rt].V;RI mid=l+r>>1;
return (p<=mid?Q(O[rt].S[0],p,l,mid):Q(O[rt].S[1],p,mid+1,r))+O[rt].V;
}
}S;
I void U(RI x,CI l,CI r,CI v) {W(x<=n) S.U(Rt[x],l,r,v),x+=x&-x;}
public:
I void U(CI l,CI r,CI v) {U(l,l,r,v),U(r+1,l,r,-v);}//区间修改
I int Q(RI x,CI y,RI t=0) {W(x) t+=S.Q(Rt[x],y),x-=x&-x;return t;}//单点查询
}T;
#define Find(x) --S.upper_bound(x)//在set中找到对应区间
I void On(CI ti,CI x)//打开
{
IT v;RI l=x,r=x;a[x+1]&&(v=Find(x+1),T.U(v->l,v->r,ti-v->t),r=v->r,S.erase(v),0),//若右边存在区间,计算贡献并删去
a[x-1]&&(v=Find(x-1),T.U(v->l,v->r,ti-v->t),l=v->l,S.erase(v),0),S.insert(V(l,r,ti));//若左边存在区间,计算贡献并删去;最后加入新区间
}
I void Off(CI ti,CI x)//关掉
{
IT v=Find(x);RI l=v->l,r=v->r;T.U(l,r,ti-v->t),S.erase(v),//计算贡献并删去
l^x&&(S.insert(V(l,x-1,ti)),0),r^x&&(S.insert(V(x+1,r,ti)),0);//断成两个新区间
}
I int Ask(CI ti,CI x,CI y)//求出尚未统计的答案
{
if(!a[x]) return 0;IT v=Find(x);return v->r>=y?ti-v->t:0;//如果在同一连通块中才有贡献
}
int main()
{
RI Qt,i,j;for(scanf("%d%d%s",&n,&Qt,s+1),i=1;i<=n;++i) a[i]=s[i]&1;//读入数据
for(i=1;i<=n;i=j+1) if(a[j=i]) {W(j^n&&a[j+1]) ++j;S.insert(V(i,j,0));}//初始化抠区间
RI x,y;for(i=1;i<=Qt;++i) switch(scanf("%s%d",s+1,&x),s[1])//处理操作
{
case 't':(a[x]^=1)?On(i,x):Off(i,x);break;//修改
case 'q':scanf("%d",&y),--y,printf("%d
",T.Q(x,y)+Ask(i,x,y));break;//询问
}return 0;
}