【[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] ...
随机推荐
- 【httpwatch】httpwatch对测试的应用
HttpWatch是一款网页数据分析工具,是浏览器插件,集成在IE浏览器的工具栏中.主要可以用来帮忙我们查看及分析HTTP请求的:Cookie.请求参数.请求头信息.响应头信息.响应状态.响应正文等内 ...
- Java中的断言 Assert
今天正好遇到了,就记一下 一.作用: 用与编写单元测试 二.assert 关键字 assert 理论上和 if类似, 但是assert 仅仅用于测试, 不能用于业务 如果发现断言无效, 则可能时ide ...
- javaEE Design Patter(1)初步了解23种常用设计模式
设计模式分为三大类: 创建型模式,共五种:工厂方法模式.抽象工厂模式.单例模式.建造者模式.原型模式. 结构型模式,共七种:适配器模式.装饰器模式.代理模式.外观模式.桥接模式.组合模式.享元模式. ...
- shell变量类型和运算符
一.shell变量的应用 1.shell变量的种类 ①用户自定义变量:由用户自己定义,修改和使用 ②预定义变量:bash预定义的特殊变量,不能直接修改 ③位置变量:通过命令行给程序传递执行参数 二.变 ...
- struts2 国际化语言转换
学习struts2,了解了使用struts2的配置文件可以走向国际化,实现页面的语言转换.我已中文和英文为例,简单的实现登录页面的国际化 废话不多说,上代码 一,login.jsp页面 使用s标签&l ...
- IE 8兼容:<meta http-equiv="X-UA-Compatible" content="IE=edge" /> X-UA-Compatible的解释
前言:9月份开始了,大四也真正的到来了.深知自己网页布局还有很大的缺陷,接下来打算从工作中抽时间出来模仿着一些互联网公司的网站.顺便把基础理论知识打好. 第一个目标:小米官网:后续会把练习的项目放到g ...
- MASQL语法大全
mysql sql语句大全 1.说明:创建数据库 CREATE DATABASE database-name 2.说明:删除数据库 drop database dbname 3.说明:备份sql se ...
- 【Python】raw转义字符
r"hi" 这里字符串前面加了r,是raw的意思,它表示对字符串不进行转义.为什么要加这个?你可以试试print "\bhi"和r"\bhi" ...
- JSON中的安全问题
Web中使用JSON时最常见的两个安全问题: 1.跨站请求伪造: 即CSRF,是一种利用站点对用户浏览器信任发起攻击的方式.典型的就是JSON数组,更多信息请自行上网百度. 2.跨站脚本攻击. 是注入 ...
- C++中精确到小数点后任意位
#include <iostream> #include <iomanip> //用setprecision(n)设置精度,其中n表示精确到小数点后n位 using names ...