同样是从试炼场点进来的,这是一道非常需要耐心的题

不过明明就是我太菜了,真正的大佬都是一眼秒吧

首先我们有一种比较常规的暴力思路,就是用线段树来维护区间连续子段数,而拒绝掉所有与当前区间相冲突的预约我们可以通过二分来做,来查找从最开始到这个区间的区间首第一个与区间首相同的位置,和区间尾到最后最靠后的一个与区间尾相同的位置

为什么可以二分呢,我们知道我们要找的子段是连续的,所以具有单调性,于是可以二分

由于我们在维护区间连续子段数和进行二分的时候需要做一个单点查询,所以时间复杂度是\(O(nlog^{2}n)\)

代码如下

#include<iostream>
#include<cstdio>
#include<cstring>
#define re register
#define maxn 200001
using namespace std;
int l[maxn<<2],r[maxn<<2],sum[maxn<<2],d[maxn<<2],tag[maxn<<2];
//sum[i]表示i对应区间内连续子段数
int n,m,tot;
char p[maxn];
int x[maxn],y[maxn];
void build(int x,int y,int i)
{
l[i]=x;
r[i]=y;
tag[i]=-1;
if(x==y) return;
int mid=x+y>>1;
build(x,mid,i<<1);
build(mid+1,y,i<<1|1);
}
inline void pushdown(int i)
{
if(tag[i]!=-1)
{
tag[i<<1]=tag[i];
tag[i<<1|1]=tag[i];
d[i<<1|1]=tag[i];
d[i<<1]=tag[i];
if(tag[i]) sum[i<<1|1]=sum[i<<1]=1;
else sum[i<<1|1]=sum[i<<1]=0;
tag[i]=-1;
}
}
int ask(int x,int i)
{
if(l[i]==r[i]&&l[i]==x) return d[i];
pushdown(i);
int mid=l[i]+r[i]>>1;
if(x<=mid) return ask(x,i<<1);
return ask(x,i<<1|1);
}//单点查询
void change(int x,int y,int i,int v)
{
if(x<=l[i]&&y>=r[i])
{
if(v) sum[i]=1;
else sum[i]=0;
tag[i]=v;
d[i]=v;
return;
}
pushdown(i);
int mid=l[i]+r[i]>>1;
if(y<=mid) change(x,y,i<<1,v);
else if(x>mid) change(x,y,i<<1|1,v);
else change(x,y,i<<1|1,v),change(x,y,i<<1,v);
int q1=ask(mid,1),q2=ask(mid+1,1);
if(q1==q2&&q1) sum[i]=sum[i<<1]+sum[i<<1|1]-1;
//在进行updata操作时我们要进行两次单点查询
//如果当前区间左儿子的最右点和右儿子的最左点相同且均不为0
//则说明有一个子段被分开了,sum[i]应该等于sum[i<<1]+sum[i<<1|1]-1;
else sum[i]=sum[i<<1|1]+sum[i<<1];
}
int query(int x,int y,int i)
{
if(x<=l[i]&&y>=r[i]) return sum[i];
pushdown(i);
int mid=l[i]+r[i]>>1;
if(y<=mid) return query(x,y,i<<1);
if(x>mid) return query(x,y,i<<1|1);
int q1=ask(mid,1),q2=ask(mid+1,1);
if(q1==q2&&q1) return query(x,y,i<<1)+query(x,y,i<<1|1)-1;
return query(x,y,i<<1)+query(x,y,i<<1|1);
d[i]=d[i<<1];
}
int main()
{
ios::sync_with_stdio(false);
cin>>n;
int xx,yy;
int maxx=0,minn=99999999;
for(re int i=1;i<=n;i++)
{
cin>>p[i];
if(p[i]=='A') cin>>x[i]>>y[i],maxx=max(maxx,y[i]),minn=min(minn,x[i]);
}
build(1,maxx,1);
for(re int i=1;i<=n;i++)
{
if(p[i]=='B') cout<<m-tot<<endl;
if(p[i]=='A')
{
int now=query(x[i],y[i],1);
cout<<now<<endl;
tot+=now;//tot为总撤销个数,方便进行B操作
int now1=ask(x[i],1),now2=ask(y[i],1);
int ans1,ans2;
int ll=minn,rr=x[i];
if(now1)//如果当前区间的最左点为0,那么就不需要进行二分了
{
while(ll<=rr)
{
int mid=ll+rr>>1;
if(ask(mid,1)==now1) ans1=mid,rr=mid-1;
else ll=mid+1;
}
}//查找从最开始到这个区间的区间首第一个与区间首相同的位置
if(now2)
{
ll=y[i],rr=maxx;
while(ll<=rr)
{
int mid=ll+rr>>1;
if(ask(mid,1)==now2) ans2=mid,ll=mid+1;
else rr=mid-1;
}
}//区间尾到最后最靠后的一个与区间尾相同的位置
if(now1&&x[i]!=ans1) change(ans1,x[i],1,0);
if(now2&&y[i]!=ans2) change(y[i],ans2,1,0);
change(x[i],y[i],1,++m);
}
}
return 0;
}

当然,这样写理论上的复杂度应该是\(O(nlog^{2}n)\),但是常数太大了,所以吸氧也不能拯救这份代码,这种写法就只有60

但是我们可以考虑对这份代码进行优化,尝试去掉一个\(log\)

首先我们在维护sum的时候,只用到了区间最左和最右位置的状态,所以我们完全可以再开两个数组\(lc\),\(rc\),分别维护区间最左和最右的位置的状态

而对于我们二分查找做撤销操作,我们可以对每个区间维护一下撤销这个区间时应该撤销的实际区间是哪一个,于是我们可以再开两个数组\(ll\),\(rr\)来进行维护每个区间做撤销操作时实际应该对哪个区间进行操作

这样一来我们通过来我们通过两次时间换空间,使我们的代码的时间复杂度降到了\(O(nlongn)\),而空间上尽管多开了几个数组,但内存仍绰绰有余

于是就是代码了

#include<iostream>
#include<cstring>
#include<cstdio>
#define re register
#define maxn 200001
using namespace std;
struct node
{
int a,b,c;
};
int l[maxn<<2],r[maxn<<2],ll[maxn<<2],rr[maxn<<2];
int rc[maxn<<2],lc[maxn<<2],sum[maxn<<2],tag[maxn<<2];
int n,m,tot;
char p[maxn];
int x[maxn],y[maxn];
void build(int x,int y,int i)
{
ll[i]=l[i]=x;
rr[i]=r[i]=y;
tag[i]=-1;
if(x==y) return;
int mid=x+y>>1;
build(x,mid,i<<1);
build(mid+1,y,i<<1|1);
}
inline void pushdown(int i)
{
if(tag[i]==-1) return;
if(tag[i])
{
sum[i<<1|1]=sum[i<<1]=1;
tag[i<<1|1]=tag[i<<1]=tag[i];
ll[i<<1]=ll[i];
ll[i<<1|1]=ll[i];
//左右两个儿子向左扩展的位置应该与i向左扩展的位置一致
rr[i<<1|1]=rr[i];
rr[i<<1]=rr[i];
//左右两个儿子向右扩展的位置应该与i向右扩展的位置一致
rc[i<<1]=rc[i<<1|1]=lc[i<<1]=lc[i<<1|1]=tag[i];
//更新状态
}
if(!tag[i])//撤销操作的下传标记
{
sum[i<<1|1]=sum[i<<1]=0;
tag[i<<1|1]=tag[i<<1]=0;
ll[i<<1]=l[i<<1];
ll[i<<1|1]=l[i<<1|1];
rr[i<<1]=r[i<<1];
rr[i<<1|1]=r[i<<1|1];
//由于又恢复到初始状态,不需要进行扩展,所以直接修改成区间的实际值就好
rc[i<<1]=rc[i<<1|1]=lc[i<<1]=lc[i<<1|1]=0;
}
tag[i]=-1;
}
void change(int x,int y,int i,int v)
{
if(x<=l[i]&&y>=r[i])
{
if(v)
{
sum[i]=1;
ll[i]=x;
rr[i]=y;
}else
{
sum[i]=0;
ll[i]=l[i];
rr[i]=r[i];
}
rc[i]=lc[i]=v;
tag[i]=v;
return;
}
pushdown(i);
int mid=l[i]+r[i]>>1;
if(y<=mid) change(x,y,i<<1,v);
else if(x>mid) change(x,y,i<<1|1,v);
else change(x,y,i<<1,v),change(x,y,i<<1|1,v);
if(rc[i<<1]==lc[i<<1|1]&&rc[i<<1]) sum[i]=sum[i<<1]+sum[i<<1|1]-1;//同上一份代码
else sum[i]=sum[i<<1|1]+sum[i<<1];
rc[i]=rc[i<<1|1];
lc[i]=lc[i<<1];
//最右边的状态来自于右儿子,最左边的状态来自于左儿子
ll[i]=ll[i<<1];
rr[i]=rr[i<<1|1];
//更新扩展的区间
}
node query(int x,int y,int i)
{
if(x<=l[i]&&y>=r[i]) return (node){sum[i],ll[i],rr[i]};
pushdown(i);
int mid=l[i]+r[i]>>1;
if(y<=mid) return query(x,y,i<<1);
if(x>mid) return query(x,y,i<<1|1);
node q1=query(x,y,i<<1),q2=query(x,y,i<<1|1);
if(rc[i<<1]==lc[i<<1|1]&&rc[i<<1]) return (node){q1.a+q2.a-1,min(q1.b,q2.b),max(q1.c,q2.c)};
return (node){q1.a+q2.a,min(q1.b,q2.b),max(q1.c,q2.c)};
}
int main()
{
ios::sync_with_stdio(false);
cin>>n;
int xx,yy;
int maxx=0,minn=99999999;
for(re int i=0;i<n;++i)
{
cin>>p[i];
if(p[i]=='A') cin>>x[i]>>y[i],maxx=max(maxx,y[i]),minn=min(minn,x[i]);
}
build(minn,maxx,1);
for(re int i=0;i<n;++i)
{
if(p[i]=='B') cout<<m-tot<<endl;
if(p[i]=='A')
{
node now=query(x[i],y[i],1);
cout<<now.a<<endl;
tot+=now.a;
if(now.a) change(now.b,now.c,1,0);
change(x[i],y[i],1,++m);
}
}
return 0;
}

【[SHOI2009]会场预约】的更多相关文章

  1. 【题解】P2161[SHOI2009]会场预约(set)

    [题解][P2161 SHOI2009]会场预约 题目很像[[题解]APIO2009]会议中心 \(set\)大法好啊! 然后我们有个小\(trick\)(炒鸡帅),就是如何优雅地判断线段交? str ...

  2. [LuoguP2161[ [SHOI2009]会场预约 (splay)

    题面 传送门:https://www.luogu.org/problemnew/show/P2161 Solution splay 的确有线段树/树状数组的做法,但我做的时候脑残没想到 我们可以考虑写 ...

  3. 2021.12.08 [SHOI2009]会场预约(平衡树游码表)

    2021.12.08 [SHOI2009]会场预约(平衡树游码表) https://www.luogu.com.cn/problem/P2161 题意: 你需要维护一个 在数轴上的线段 的集合 \(S ...

  4. [SHOI2009] 会场预约 - Treap

    Description PP大厦有一间空的礼堂,可以为企业或者单位提供会议场地.这些会议中的大多数都需要连续几天的时间(个别的可能只需要一天),不过场地只有一个,所以不同的会议的时间申请不能够冲突.也 ...

  5. BZOJ2028: [SHOI2009]会场预约(set)

    Time Limit: 20 Sec  Memory Limit: 64 MBSubmit: 425  Solved: 213[Submit][Status][Discuss] Description ...

  6. P2161 [SHOI2009]会场预约

    题目描述 PP大厦有一间空的礼堂,可以为企业或者单位提供会议场地.这些会议中的大多数都需要连续几天的时间(个别的可能只需要一天),不过场地只有一个,所以不同的会议的时间申请不能够冲突.也就是说,前一个 ...

  7. P2161 [SHOI2009]会场预约 (线段树:线段树上的不重复覆盖数)

    题目描述 PP大厦有一间空的礼堂,可以为企业或者单位提供会议场地.这些会议中的大多数都需要连续几天的时间(个别的可能只需要一天),不过场地只有一个,所以不同的会议的时间申请不能够冲突.也就是说,前一个 ...

  8. P2161 [SHOI2009]会场预约[线段树/树状数组+二分/STL]

    题目描述 PP大厦有一间空的礼堂,可以为企业或者单位提供会议场地.这些会议中的大多数都需要连续几天的时间(个别的可能只需要一天),不过场地只有一个,所以不同的会议的时间申请不能够冲突.也就是说,前一个 ...

  9. LuoguP2161 [SHOI2009]会场预约

    题目地址 题目链接 题解 用fhqtreap对区间进行维护. 可以注意到的是,对于当前存在的预约,他们一定是升序排列的(有重叠的都被删了). 那么就可以用按照位置分裂的fhqtreap搞了(预约无论按 ...

  10. Luogu2161 [SHOI2009]会场预约-线段树

    Solution 线段树维护 sum 表示区间内预约个数, L 表示区间最左边的预约, R 表示区间最右边的预约. $pushup$ 就是这样 : void up(int nd) { sum[nd] ...

随机推荐

  1. java.lang.RuntimeException Unable to instantiate application Caused by: java

    参考:http://blog.csdn.net/xufazhong/article/details/71155528 dependencies { classpath 'com.android.too ...

  2. 2017年11月30日 C#TreeNode递归&邮箱验证&新用户窗体

    TreeNode递归 递归:自己调用自己一层一层的把数据找出来 TreeNode:可以创建多个节点 private void button1_Click(object sender, EventArg ...

  3. 二:Nexus知识

    Nexus访问 Http://localhost:8080/nexus Nexus登陆 用户:admin 密码:admin123 Nexus仓库 nexus的仓库类型分为以下四种: group: 仓库 ...

  4. 限流(四)nginx接入层限流

    一.nginx限流模块 接入层指的是请求流量的入口,我们可以在这里做很多控制,比如:负载均衡,缓存,限流等. nginx中针对限流有两个模块可以处理: 1)ngx_http_limit_req_mod ...

  5. 十三、nginx 强制下载txt等文件

    当前的浏览器能够识别文件格式,如果浏览器本身能够解析就会默认打开,如果不能解析就会下载该文件. 那么使用nginx做资源服务器的时候,如何强制下载文件呢? location /back/upload/ ...

  6. MySQL批量插入多条数据方便测试

    批量插入流程 数据库字段 delimiter create procedure doinsert3() begin declare i int; declare j int; ; ; ) do ins ...

  7. Mysql根据经纬度筛选数据

    创建位置表,并且插入测试数据 /*     Navicat MySQL Data Transfer     Source Server         : localhost     Source S ...

  8. Java 线程--继承java.lang.Thread类实现线程

    现实生活中的很多事情是同时进行的,Java中为了模拟这种状态,引入了线程机制.先来看线程的基本概念. 线程是指进程中的一个执行场景,也就是执行流程,进程和线程的区别: 1.每个进程是一个应用程序,都有 ...

  9. 虚拟机下centos时间不正确的方便解决方法

    就是用NTP了,通过外部的服务同步时间. ntpdate us.pool.ntp.org | logger -t NTP 如果没有ntpdate ,可以使用 yum install ntpdate 进 ...

  10. 纯CSS实现立方体旋转

    下面为通过CSS动画实现的立方体旋转,可以改变CSS代码中关键帧定义(@keyframes)来改变立方体的旋转方式 HTML部分: <body class="body"> ...