zoukankan      html  css  js  c++  java
  • 【洛谷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
    ",T.Q(x,y)+Ask(i,x,y));break;//询问
    	}return 0;
    }
    
  • 相关阅读:
    Hibernate Validator
    RocketMQ之八:重试队列,死信队列,消息轨迹
    使用hibernate validator出现
    Hibernate Validator--创建自己的约束规则
    Java应用中使用ShutdownHook友好地清理现场、退出JVM的2种方法
    笔者带你剖析轻量级Sharding中间件——Kratos1.x
    [caffe]深度学习之图像分类模型VGG解读
    类的载入机制
    机器人api(图灵机器人)
    回文串问题总结
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu5445.html
Copyright © 2011-2022 走看看