[NOIP2017]列队 (Splay)
NOIP2017真的是不按常理出牌:
1、数学题不在Day2T1
2、一道水题一道细节极多的模拟题一道不知道怎么形容的题(小凯的疑惑)(因为我太菜了)
3、3道大火题
当时看到列队这题是毫无头绪的,因为数据大得让你存都存不下,于是果断打了个30分暴力(如果打个离散化还能多骗20分)。
蓦然回首,豁然开朗。
思考可知,一个人出列影响的只是当前一行和最后一列,于是,我们只需要对每一行和最后一列分别用数据结构维护,这个数据结构要支持
1、查询并删除第\(k\)个数。
2、在末尾插入一个数。
若能维护这些,设每次操作的位置为\((x,y)\),那我们只需要把第\(x\)行第\(y\)个数删除并插入到最后一列的末尾去,然后把最后一列第\(x\)个数删除并插入到第\(x\)行的末尾就行了。
接下来考虑用什么数据结构。
\(Splay\)显然是支持这些操作的(事实上几乎所有平衡树都支持,然而我只会常数巨大的\(Splay\))
然后面临的一个问题就是\(n*m\)的范围达到了\((30W)^2\),显然直接开是开不下的。
容易发现\(q\)次操作后,绝大部分人的相对位置是不变的,所以我们可以用点表示区间,查询&删除\(k\)时把这个区间\([l,r]\)分成三部分,
·1、\([l,k-1]\)
·2、\(k\)
·3、\([k+1,r]\)
第一个区间就是原来的点,第三个区间是新加的点,第二个就是我们需要的,直接丢掉,把第一个和第三个点连起来(具体就是把第三部分插入到第一部分和第一部分的右儿子之间)。特别地,如果\(k\)刚好等于这个点维护的区间的两个端点之一,直接把端点维护的区间减一减掉\(k\)就行了。
\(100\)多\(line\)的\(code\),\(debug\)了好久唔唔唔。
#include <cstdio>
#include <cstdlib>
#define ll long long
#define getchar() (S==T&&(T=(S=BB)+fread(BB,1,1<<15,stdin),S==T)?EOF:*S++)
char BB[1<<15],*S=BB,*T=BB;
inline int read(){
int s = 0, w = 1;
char ch = getchar();
while(ch < '0' || ch > '9'){ if(ch == '-') w = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
return s * w;
}
const int MAXN = 300010;
const int MAX = 10000010;
int n, m, q;
int cnt, size[MAX], son[MAX][2], fa[MAX];
ll L[MAX], R[MAX];
struct Splay_Tree{
int root;
inline void pushup(int x){ size[x] = size[son[x][0]] + size[son[x][1]] + R[x] - L[x] + 1; }
inline int Newnode(ll l, ll r){
L[++cnt] = l; R[cnt] = r;size[cnt] = r - l + 1;
return cnt;
}
inline void init(ll l, ll r){ root = Newnode(l, r); }
inline void rotate(int x){
int y = fa[x]; int z = fa[y]; int k = (son[y][1] == x);
son[z][son[z][1] == y] = x; fa[x] = z;
son[y][k] = son[x][k ^ 1]; fa[son[x][k ^ 1]] = y;
son[x][k ^ 1] = y; fa[y] = x;
pushup(y); pushup(x);
}
inline void Splay(int x, int goal){
if(x == goal) return;
int y, z;
while(fa[x] != goal){
y = fa[x]; z = fa[y];
if(z != goal) (son[z][1] == y) ^ (son[y][1] == x) ? rotate(x) : rotate(y);
rotate(x);
}
if(!goal) root = x;
}
inline void insert(ll x){
int now = root;
while(son[now][1]) now = son[now][1];
int t = Newnode(x, x);
son[now][1] = t; fa[t] = now; Splay(t, 0);
}
inline ll split(int x, ll k){ //分裂点x,删除k
ll s = L[x], t = R[x];
if(k != s && k != t){
int b = Newnode(k + 1, t); R[x] = k - 1;
son[b][1] = son[x][1]; fa[son[x][1]] = b;
son[x][1] = b; fa[b] = x;
Splay(b, 0);
}
else{
if(k == t) --R[x];
else ++L[x];
--size[x];
Splay(x, 0);
}
return k;
}
inline ll popkth(int k){ //删除并返回第k大
int x = root;
while(233){
if(size[son[x][0]] >= k) x = son[x][0];
else {
k -= size[son[x][0]];
if(k <= R[x] - L[x] + 1)
return split(x, k + L[x] - 1);
k -= R[x] - L[x] + 1; x = son[x][1];
}
}
}
inline int build(int l, int r, int f){ //对最后一列的建树
if(l > r) return 0;
int mid = (l + r) >> 1;
int now = Newnode((ll)mid * m, (ll)mid * m);
fa[now] = f;
son[now][0] = build(l, mid - 1, now);
son[now][1] = build(mid + 1, r, now);
pushup(now);
return now;
}
}splay[MAXN];
int a, b;
ll x, y;
int main(){
n = read(); m = read(); q = read();
for(int i = 1; i <= n; ++i) //每行一棵Splay维护前m-1个数
splay[i].init((ll)(i - 1) * m + 1, (ll)i * m - 1); //点表示区间
splay[0].root = splay[0].build(1, n, 0);
for(int i = 1; i <= q; ++i){
a = read(); b = read();
if(b == m){
printf("%lld\n", x = splay[0].popkth(a));
splay[0].insert(x);
continue;
}
printf("%lld\n", x = splay[a].popkth(b));
splay[a].insert(splay[0].popkth(a));
splay[0].insert(x);
}
return 0;
}
[NOIP2017]列队 (Splay)的更多相关文章
- Luogu 3960 [NOIP2017] 列队 - splay|线段树
题解 是我从来没有做过的裂点splay... 看的时候还是很懵逼的QAQ. 把最后一列的$n$个数放在一个平衡树中, 有 $n$ 个点 剩下的$n$行数, 每行都开一个平衡树,开始时每棵树中仅有$1$ ...
- 【loj2319】[NOIP2017]列队 Splay(卡过)
题目描述 给出一个 $n\times m$ 的矩阵,第 $i$ 行第 $j$ 列的数为 $(i-1)\times m+j$ . 现在有 $q$ 次操作,每次操作给出位置 $(x,y)$ ,取出 $(x ...
- [NOIP2017]列队 离线+SBT
[NOIP2017]列队 题目描述 Sylvia 是一个热爱学习的女♂孩子. 前段时间,Sylvia 参加了学校的军训.众所周知,军训的时候需要站方阵. Sylvia 所在的方阵中有n×m名学生,方阵 ...
- 题解[NOIP2017] 列队
题解[NOIP2017] 列队 题面 解析 看到这题时感觉这个编号很难维护啊? 后来看了lzf大佬的题解才会.. 首先,考虑一个稍微暴力的做法, 维护每一行的前\(m-1\)个人和最后一列的\(n\) ...
- 【NOIP2017】列队 splay
当年太菜了啊,连$60$分的暴力都没拿满,只打了一个$30$分的. 考虑到这题最多只会询问到$30W$个点,且整个矩阵会去到$30W\times 30W$,显然不能将所有的点存下来. 对于每一行(除最 ...
- [NOIP2017]列队(线段树/裂点splay)
考虑n=1的做法,就是支持: 1.在线删一个数 2.在结尾加一个数 3.查询序列的第y个数 用线段树记录区间内被删元素的个数,可以通过线段树上二分快速得解,对于新增的数,用vector记录即可. 对于 ...
- Luogu3960 NOIP2017列队(splay/线段树)
令splay中的一个点表示一段区间,需要使用其中某个点时将区间分裂即可,剩下的都是splay的基本操作了.写的非常丑陋,注意细节.感觉考场上肯定只能靠部分分苟活了.想起来去年因为各种莫名其妙的原因50 ...
- NOIP2017列队(phalanx)解题报告
列队作为NOIP2017最后一道题,其实并不难,只是相对于其它题目,有点小小的工业 首先,这道题我用splay维护的,如果你不会splay,又想学一下splay,可以来这里学一学,接下来步入正题 首先 ...
- NOIP2017 列队 题解报告【56行线段树】
题目描述 Sylvia 是一个热爱学习的女♂孩子. 前段时间,Sylvia 参加了学校的军训.众所周知,军训的时候需要站方阵. Sylvia 所在的方阵中有n \times mn×m名学生,方阵的行数 ...
随机推荐
- 云计算之路-阿里云上:Web服务器请求到达量突降
今天下午遇到了自使用阿里云以来首次遇到的新情况——http.sys的ArrivalRate突降(说明请求到达IIS的请求数量少了),而且SLB中的3台ECS都出现了这个问题. 1. 10.161.24 ...
- Android学习笔记之,调用系统图库,添加自定义字体,屏幕截图
新年开始的第一天就来学习了慕课迎春活动中的Android心愿分享一课,学到了几个知识点,在此记录一下. 1.调用系统图库调用系统图库用的是intent,步骤为弹出系统图库选择器,选择图片后获取到所选择 ...
- Android4.0系统以上程序不出现菜单键的问题解决
去掉targetSdkVersion 或改为targetSdkVersion =13或更小.. 不改targetSdkVersion的办法:在onCreate() 里setContentView()之 ...
- Git 上传本地仓库到码云
一.将本地的项目上传到码云 1.码云上创建一个项目 testgit (名字随你) 2.本地创建一个文件夹D:/testgit,然后使用git bash 3.cd 到本地文件夹中D:/testgit 4 ...
- Mysql性能优化三:主从配置,读写分离
大型网站为了软解大量的并发访问,除了在网站实现分布式负载均衡,远远不够.到了数据业务层.数据访问层,如果还是传统的数据结构,或者只是单单靠一台服务器扛,如此多的数据库连接操作,数据库必然会崩溃,数据丢 ...
- PAT——乙级1006:换个格式输出整数&乙级1021:个位数统计&乙级1031:查验身份证
1006 换个格式输出整数 (15 point(s)) 让我们用字母 B 来表示“百”.字母 S 表示“十”,用 12...n 来表示不为零的个位数字 n(<10),换个格式来输出任一个不超过 ...
- Drools 7.4.1.Final参考手册(十四)集成Spring
集成Spring Drools 6.0重要变更 Drools Spring集成经历了与Drools 6.0的变化完全一致的改造. 以下是一些主要的变化: T*推荐的Drools Spring的前缀已经 ...
- Win7下搭建Zigbee开发环境
操作系统:64位Win7 芯片类型:Texas Instruments的CC2530 软件平台:IAR v8.10 Zigbee协议栈:ZStack-CC2530-2.5.1a CP2102 USB ...
- Uva 12627 Erratic Expansion(递归)
这道题大体意思是利用一种递归规则生成不同的气球,问在某两行之间有多少个红气球. 我拿到这个题,一开始想的是递归求解,但在如何递归求解的思路上我的方法是错误的.在研读了例题上给出的提示后豁然开朗(顺便吐 ...
- [剑指Offer] 9.变态跳台阶
题目描述 一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级.求该青蛙跳上一个n级的台阶总共有多少种跳法. [思路1]每个台阶都有跳与不跳两种可能性(最后一个台阶除外),最后一个台阶必 ...