Luogu 3960 [NOIP2017] 列队 - splay|线段树
题解
是我从来没有做过的裂点splay。。。 看的时候还是很懵逼的QAQ。
把最后一列的$n$个数放在一个平衡树中, 有 $n$ 个点
剩下的$n$行数, 每行都开一个平衡树,开始时每棵树中仅有$1$个点, 记录了开始时的区间左端点 $1$ 和右端点$m - 1$。
这样每次出队都最多只会影响两棵平衡树, 其中一颗为表示最后一列的平衡树。
然后就可以分成两种情况进行处理
1. $y = m$ 即出队的人位于最后一列,那么仅会影响最后一列的那颗平衡树, 删除位于第$x$个位置的点, 再插入到最后即可
2. $y \ne m$ 首先把最后一列的平衡树的第$x$个节点 $tmp$ 删除,
然后将第$x$棵平衡树 分成$[1,y - 1]$,$ y $,$ [y +1,m - 1] $三个区间, 再将y删除并插入到 最后一列平衡树的最后。
最后再把tmp插入到第$x$ 棵平衡树的最后。
虽然看起来很暴力, 但因为我太弱还是想不到TAT
代码
#include<cstring>
#include<algorithm>
#include<cstdio>
#define rd read()
#define ri register int
#define il inline
using namespace std;
typedef long long ll; const int N = 3e5 + 1e4; int sz[N << ], cnt[N << ], l[N << ], r[N << ], root[N]; //root为每颗平衡树的根节点
int ch[N << ][], f[N << ];
ll key[N << ];//key为节点对应的人的编号
int n, m, q, tot; int read() {
int X = , p = ; char c = getchar();
for(; c > '' || c < ''; c = getchar() ) if( c == '-' ) p = -;
for(; c >= '' && c <= ''; c = getchar() ) X = X * + c - '';
return X * p;
} il void update( int x ) {
sz[x] = cnt[x];
sz[x] += sz[ch[x][]] + sz[ch[x][]];
} il int get( int x ) {
return ch[f[x]][] == x;
} int build( int l, int r , int fa ) {//最后一列的平衡树
if( l > r ) return ;
int mid = (l + r) >> ;
sz[mid] = cnt[mid] = ;
key[mid] = 1LL * mid * m;//编号
f[mid] = fa;
ch[mid][] = build( l, mid - , mid );
ch[mid][] = build( mid + , r, mid );
update(mid);
return mid;
} void rotate( int x ) {
int old = f[x], oldf = f[old], son = ch[x][get(x) ^ ];
if(oldf) ch[oldf][get(old)] = x;
ch[x][get(x) ^ ] = old;
ch[old][get(x)] = son;
f[old] = x; f[x] = oldf;
if(son) f[son] = old;
update(old); update(x);
} void splay( int x, int id ) {//id表示平衡树的编号
for( int fa; (fa = f[x]); rotate(x))
if(f[fa]) rotate(get(fa) == get(x) ? fa : x);
root[id] = x;
} int findk( int k , int now ) {//查询最后一列的平衡树中的第k个点
for(; ;) {
if( k <= sz[ch[now][]] ) now = ch[now][];
else if( k == sz[ch[now][]] + cnt[now] ) return now;
else {
k -= sz[ch[now][]] + cnt[now];
now = ch[now][];
}
}
} void insert_last( int id, ll x ) {//向第id棵平衡树的末尾插入key为x 的点
if(!root[id]) {
root[id] = ++tot;
key[tot] = x;
sz[tot] = cnt[tot] = ;
ch[tot][] = ch[tot][] = ;
return;
}
int now = root[id];
for(; ch[now][]; now = ch[now][] );
f[++tot] = now;
ch[tot][] = ch[tot][] = ;
ch[now][] = tot;
sz[tot] = cnt[tot] = ;
key[tot] = x;
update(now);
splay(tot, id);
} int pre( int id ) {//前驱
int now = ch[root[id]][];
for(; ch[now][]; now = ch[now][] );
return now;
} void del( int id ) {//删除第id棵平衡树的根节点
int rt = root[id];
if(!ch[rt][] && !ch[rt][]) {
root[id] = ;
return;
}
if(!ch[rt][]) {
root[id] = ch[rt][];
f[root[id]] = ;
return;
}
if(!ch[rt][]) {
root[id] = ch[rt][];
f[root[id]] = ;
return;
}
int pr = pre(id);
splay(pr, id);
ch[pr][] = ch[rt][];
f[ch[rt][]] = pr;
update(pr);
} void split( int k, int now, int id ) {//将第id棵平衡树分裂
if( k <= sz[ch[now][]] ) {
split(k, ch[now][], id);
return;
}
if( k > sz[ch[now][]] + cnt[now] ) {
split(k - sz[ch[now][]] - cnt[now], ch[now][], id);
return;
}
k -= sz[ch[now][]];
if( k != ) {
sz[++tot] = k - ;
cnt[tot] = k - ;
f[tot] = now;
cnt[now] -= k - ;
ch[tot][] = ch[now][];
f[ch[now][]] = tot;
ch[now][] = tot;
l[tot] = l[now];
r[tot] = l[tot] + k - ;
l[now] = r[tot] + ;
update(tot);
}
if(cnt[now] != ) {
sz[++tot] = cnt[now] - ;
cnt[tot] = cnt[now] - ;
f[tot] = now;
cnt[now] = ;
ch[tot][] = ch[now][];
f[ch[now][]] = tot;
ch[now][] = tot;
r[tot] = r[now];
r[now] = l[now];
l[tot] = l[now] + ;
update(tot);
}
splay(now, id);
} int main()
{
n = rd, m = rd; q = rd;
root[] = build( , n, );
tot = n;
for( int i = ; i <= n; ++i ) {
root[i] = ++tot;
sz[tot] = cnt[tot] = m - ;
l[tot] = ; r[tot] = m - ;
}
for(; q; --q ) {
int x = rd, y = rd, tmp = findk(x, root[]);
ll ans;
splay(tmp, );
del();//删除tmp
if( y != m ) {
split(y, root[x], x);
int rt = root[x];
if(!key[rt]) key[rt] = 1LL * (x - ) * m + l[rt];
ans = key[rt];
del(x);
insert_last(x, key[tmp]);//向第x棵平衡树插入,表示左移
insert_last(, ans);//向最后一列的平衡树插入,表示将位于x,y的人拉到最后
}
else {
insert_last(, key[tmp]);
ans = key[tmp];
}
printf("%lld\n", ans);
}
}
更新。。。
用动态开点线段树做了一波
和splay差不多的思想, 先不把线段树中的点都开满,有$N+1$个空树。
定义$sum$为删除的数,每次查询第$k$个数时, 把$k$与 mid - l + 1 - sum[lson[nd]] 进行比较, 进入相应子树继续查询。
添加时就用vector插入。
删除时只要添加节点并记录
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define ll long long
#define rd read()
using namespace std; const int N = 3e6; int n, m, q, tot;
int rt[N << ], ls[N << ], rs[N << ], sum[N << ];
int pos, mx; vector<ll> v[N]; int read() {
int X = , p = ; char c = getchar();
for(; c > '' || c < ''; c = getchar()) if(c == '-') p = -;
for(; c >= '' && c <= ''; c = getchar()) X = X * + c - '';
return X * p;
} void change(int &o, int l, int r) {
if(!o) o = ++tot;
sum[o]++;
if(l == r) return;
int mid = (l + r) >> ;
if(pos <= mid) change(ls[o], l, mid);
else change(rs[o], mid + , r);
} int query(int nd, int l, int r, int k) {
if(l == r) return l;
int mid = (l + r) >> , tmp = mid - l + - sum[ls[nd]];
if(k <= tmp) return query(ls[nd], l, mid, k);
else return query(rs[nd], mid + , r, k - tmp);
} ll work1(int x, ll y) {
pos = query(rt[n + ], , mx, x);
change(rt[n + ], , mx);
ll ans = pos <= n ? 1LL * m * pos : v[n + ][pos - n - ];
v[n + ].push_back(y ? y : ans);
return ans;
} ll work2(int x, int y) {
pos = query(rt[x], , mx, y);
change(rt[x], , mx);
ll ans = pos < m ? 1LL * (x - ) * m + pos : v[x][pos - m];
v[x].push_back(work1(x, ans));
return ans;
} int main()
{
n = rd; m = rd; q = rd;
mx = max(n, m) + q;
for(; q; q--) {
int x = rd, y = rd;
if(y == m) printf("%lld\n", work1(x, ));
else printf("%lld\n", work2(x, y));
}
}
Luogu 3960 [NOIP2017] 列队 - splay|线段树的更多相关文章
- noip2017列队(线段树)
维护一个方阵,支持 1.删掉一个点,剩下的点先向左看齐再向前看齐 2.询问一个位置上是哪个点 $n,m,q \leq 3 \times 10^5$ sol: 我们每行前$m-1$列维护一个线段树,最后 ...
- [UOJ#334][NOIP2017]列队 平衡树/线段树/树状数组
题目链接 题意不说了,一辈子也忘不掉 解法1.平衡树 这题就是平衡树裸题,每一行开一棵维护前 \(m-1\) 个,最后一列单独维护,因为很多人没有用到,所以平衡树每个节点是一个区间(pair),分裂时 ...
- NOIp2017 列队(线段树)
嘛..两年前的题目了,想起第一次参加提高组还骗了一个省二回来呢...跟同学吹了好久的... 离退役又近了一骗博客啊.. 闲聊结束. 照常化简:给定一个1-n*m编号的矩阵,每次删除一个位置,然后左边向 ...
- luogu P2574 XOR的艺术 (线段树)
luogu P2574 XOR的艺术 (线段树) 算是比较简单的线段树. 当区间修改时.\(1 xor 1 = 0,0 xor 1 = 1\)所以就是区间元素个数减去以前的\(1\)的个数就是现在\( ...
- 【原创】洛谷 LUOGU P3373 【模板】线段树2
P3373 [模板]线段树 2 题目描述 如题,已知一个数列,你需要进行下面两种操作: 1.将某区间每一个数加上x 2.将某区间每一个数乘上x 3.求出某区间每一个数的和 输入输出格式 输入格式: 第 ...
- 【原创】洛谷 LUOGU P3372 【模板】线段树1
P3372 [模板]线段树 1 题目描述 如题,已知一个数列,你需要进行下面两种操作: 1.将某区间每一个数加上x 2.求出某区间每一个数的和 输入输出格式 输入格式: 第一行包含两个整数N.M,分别 ...
- Luogu3960 NOIP2017列队(splay/线段树)
令splay中的一个点表示一段区间,需要使用其中某个点时将区间分裂即可,剩下的都是splay的基本操作了.写的非常丑陋,注意细节.感觉考场上肯定只能靠部分分苟活了.想起来去年因为各种莫名其妙的原因50 ...
- luogu P3285 [SCOI2014]方伯伯的OJ splay 线段树
LINK:方伯伯的OJ 一道稍有质量的线段树题目.不写LCT splay这辈子是不会单独写的 真的! 喜闻乐见的是 题目迷惑选手 \(op==1\) 查改用户在序列中的位置 题目压根没说位置啊 只有排 ...
- 2018.11.01 loj#2319. 「NOIP2017」列队(线段树)
传送门 唉突然回忆起去年去noipnoipnoip提高组试水然后省二滚粗的悲惨经历... 往事不堪回首. 所以说考场上真的有debuffdebuffdebuff啊!!!虽然当时我也不会权值线段树 这道 ...
随机推荐
- 了解vue
什么是Vuex Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式.它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化. 状态,其实指的是实例之间的 ...
- 【求助】win 2008 R2 远程桌面多用户,破解最大连接数2的限制
[求助]win 2008 R2 远程桌面多用户,破解最大连接数2的限制. 1. 本地组策略设置的是“允许的RD最大连接数 5”. 2. 远程桌面仍然只能有两个连接在线. 3. 后来发现是下面这个设置限 ...
- 内置锁(一)synchronized 介绍与用法
一.synchronized 的介绍 synchronized 是 Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码,而这段代码也被称 ...
- sklearn的estimator
estimator的工作流程 在sklearn中,估计器(estimator)是一个重要的角色,分类器和回归器都属于estimator.在估计器中有有两个重要的方法是fit和transform. fi ...
- OpenACC 异步计算
▶ 按照书上的例子,使用 async 导语实现主机与设备端的异步计算 ● 代码,非异步的代码只要将其中的 async 以及第 29 行删除即可 #include <stdio.h> #in ...
- 61. oracle给用户解锁
1.查看用户状态select username,account_status from dba_users where username='test'; 2.解锁: ALTER USER YS_ADM ...
- Redis主从复制原理
前言: 和MySQL主从复制的原因一样,Redis虽然读取写入的速度都特别快,但是也会产生读压力特别大的情况.为了分担读压力,Redis支持主从复制,Redis的主从结构可以采用一主多从或者级联结构, ...
- eclipse 断点找到同名的其它类
转载自Eclipse断点进入另一个项目的同名Java文件中(http://tunps.com/p/11789.html) eclipse 断点找到同名的其它类 A和B是两个相同的项目,A一直本地,B是 ...
- 基于OpenGL编写一个简易的2D渲染框架-04 绘制图片
阅读文章前需要了解的知识,纹理:https://learnopengl-cn.github.io/01%20Getting%20started/06%20Textures/ 过程简述:利用 FreeI ...
- Redis基本操作-list
Redis的5种数据结构:string.list.hash.set和zset; Redis 所有的数据结构都是以唯一的 key 字符串作为名称,然后通过这个唯一 key 值来获取相应的 value 数 ...