【[SHOI2009]会场预约】
同样是从试炼场点进来的,这是一道非常需要耐心的题
不过明明就是我太菜了,真正的大佬都是一眼秒吧
首先我们有一种比较常规的暴力思路,就是用线段树来维护区间连续子段数,而拒绝掉所有与当前区间相冲突的预约我们可以通过二分来做,来查找从最开始到这个区间的区间首第一个与区间首相同的位置,和区间尾到最后最靠后的一个与区间尾相同的位置
为什么可以二分呢,我们知道我们要找的子段是连续的,所以具有单调性,于是可以二分
由于我们在维护区间连续子段数和进行二分的时候需要做一个单点查询,所以时间复杂度是\(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]会场预约】的更多相关文章
- 【题解】P2161[SHOI2009]会场预约(set)
[题解][P2161 SHOI2009]会场预约 题目很像[[题解]APIO2009]会议中心 \(set\)大法好啊! 然后我们有个小\(trick\)(炒鸡帅),就是如何优雅地判断线段交? str ...
- [LuoguP2161[ [SHOI2009]会场预约 (splay)
题面 传送门:https://www.luogu.org/problemnew/show/P2161 Solution splay 的确有线段树/树状数组的做法,但我做的时候脑残没想到 我们可以考虑写 ...
- 2021.12.08 [SHOI2009]会场预约(平衡树游码表)
2021.12.08 [SHOI2009]会场预约(平衡树游码表) https://www.luogu.com.cn/problem/P2161 题意: 你需要维护一个 在数轴上的线段 的集合 \(S ...
- [SHOI2009] 会场预约 - Treap
Description PP大厦有一间空的礼堂,可以为企业或者单位提供会议场地.这些会议中的大多数都需要连续几天的时间(个别的可能只需要一天),不过场地只有一个,所以不同的会议的时间申请不能够冲突.也 ...
- BZOJ2028: [SHOI2009]会场预约(set)
Time Limit: 20 Sec Memory Limit: 64 MBSubmit: 425 Solved: 213[Submit][Status][Discuss] Description ...
- P2161 [SHOI2009]会场预约
题目描述 PP大厦有一间空的礼堂,可以为企业或者单位提供会议场地.这些会议中的大多数都需要连续几天的时间(个别的可能只需要一天),不过场地只有一个,所以不同的会议的时间申请不能够冲突.也就是说,前一个 ...
- P2161 [SHOI2009]会场预约 (线段树:线段树上的不重复覆盖数)
题目描述 PP大厦有一间空的礼堂,可以为企业或者单位提供会议场地.这些会议中的大多数都需要连续几天的时间(个别的可能只需要一天),不过场地只有一个,所以不同的会议的时间申请不能够冲突.也就是说,前一个 ...
- P2161 [SHOI2009]会场预约[线段树/树状数组+二分/STL]
题目描述 PP大厦有一间空的礼堂,可以为企业或者单位提供会议场地.这些会议中的大多数都需要连续几天的时间(个别的可能只需要一天),不过场地只有一个,所以不同的会议的时间申请不能够冲突.也就是说,前一个 ...
- LuoguP2161 [SHOI2009]会场预约
题目地址 题目链接 题解 用fhqtreap对区间进行维护. 可以注意到的是,对于当前存在的预约,他们一定是升序排列的(有重叠的都被删了). 那么就可以用按照位置分裂的fhqtreap搞了(预约无论按 ...
- Luogu2161 [SHOI2009]会场预约-线段树
Solution 线段树维护 sum 表示区间内预约个数, L 表示区间最左边的预约, R 表示区间最右边的预约. $pushup$ 就是这样 : void up(int nd) { sum[nd] ...
随机推荐
- Splunk和ELK深度对比
转自:http://blog.51cto.com/splunkchina/1948105 日志处理两大生态Splunk和ELK深度对比 heijunmasd 0人评论 5312人阅读 2017-07- ...
- C#之RabbitMQ系列(一)
RabbitMQ–环境搭建 MQ MQ全称为Message Queue, 消息队列(MQ)是一种应用程序对应用程序的通信方法.应用程序通过读写出入队列的消息(针对应用程序的数据)来通信,而无需专用连接 ...
- window.open()被浏览器拦截问题汇总
一.问题描述 最近在做项目的时候碰到了使用window.open被浏览器拦截的情况,虽然在自己的环境可以对页面进行放行,但是对用户来说,不能要求用户都来通过拦截.何况当出现拦截时,很多用户根本不知道发 ...
- [android] 界面切换的简单动画
1. 新建个位移动画的xml文件 Activity中开启动画 使用AnimationUtils类加载动画资源文件 left_to_right.xml <?xml version="1. ...
- 八、cent OS下tomcat启用APR并发模式
Tomcat支持三种接收请求的处理方式: BIO.NIO.APR ,本文记录tomcat配置APR模式,也是首选的模式.(Tomcat7 或以下,在 Linux 系统中默认使用BIO方式) 安装依赖库 ...
- js数组的splice函数
一直没搞懂数组的splice函数,今天稍微测试了一下,了解了它的功能,在这里记录一下 1.测试 测试① var a = [1,2,3]; console.info(a.splice(1,1)); co ...
- JS之捕获冒泡和事件委托
一.事件流(捕获,冒泡) 事件流:指从页面中接收事件的顺序,有冒泡流和捕获流. 当页面中发生某种事件(比如鼠标点击,鼠标滑过等)时,毫无疑问子元素和父元素都会接收到该事件,可具体顺序是怎样的呢?冒 ...
- Django Cookie于Session
一.Cookie与Session由来 因为Http协议的特性,每一次来自用户浏览器的请求都是无状态且独立的,通俗地说,就是无法保存用户状态,后台服务器根本就不知道当前请求和以前及以后请求是否来自同一用 ...
- JavaScript有限状态机实现方式
阮一峰博客 http://www.ruanyifeng.com/blog/2013/09/finite-state_machine_for_javascript.html 开源实现库(javascri ...
- VC6.0开发OCX按钮控件
原文:http://www.cnblogs.com/joinclear/archive/2013/05/21/3091934.html 0前言 1.OCX是典型的ActiveX控件,常见的OCX控件有 ...