把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷5445】[APIO2019] 路灯(树套树)

点此看题面

大致题意:\(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\n",T.Q(x,y)+Ask(i,x,y));break;//询问
	}return 0;
}
posted @ 2020-07-25 20:29  TheLostWeak  阅读(138)  评论(0编辑  收藏  举报