题解

是我从来没有做过的裂点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|线段树的更多相关文章

  1. noip2017列队(线段树)

    维护一个方阵,支持 1.删掉一个点,剩下的点先向左看齐再向前看齐 2.询问一个位置上是哪个点 $n,m,q \leq 3 \times 10^5$ sol: 我们每行前$m-1$列维护一个线段树,最后 ...

  2. [UOJ#334][NOIP2017]列队 平衡树/线段树/树状数组

    题目链接 题意不说了,一辈子也忘不掉 解法1.平衡树 这题就是平衡树裸题,每一行开一棵维护前 \(m-1\) 个,最后一列单独维护,因为很多人没有用到,所以平衡树每个节点是一个区间(pair),分裂时 ...

  3. NOIp2017 列队(线段树)

    嘛..两年前的题目了,想起第一次参加提高组还骗了一个省二回来呢...跟同学吹了好久的... 离退役又近了一骗博客啊.. 闲聊结束. 照常化简:给定一个1-n*m编号的矩阵,每次删除一个位置,然后左边向 ...

  4. luogu P2574 XOR的艺术 (线段树)

    luogu P2574 XOR的艺术 (线段树) 算是比较简单的线段树. 当区间修改时.\(1 xor 1 = 0,0 xor 1 = 1\)所以就是区间元素个数减去以前的\(1\)的个数就是现在\( ...

  5. 【原创】洛谷 LUOGU P3373 【模板】线段树2

    P3373 [模板]线段树 2 题目描述 如题,已知一个数列,你需要进行下面两种操作: 1.将某区间每一个数加上x 2.将某区间每一个数乘上x 3.求出某区间每一个数的和 输入输出格式 输入格式: 第 ...

  6. 【原创】洛谷 LUOGU P3372 【模板】线段树1

    P3372 [模板]线段树 1 题目描述 如题,已知一个数列,你需要进行下面两种操作: 1.将某区间每一个数加上x 2.求出某区间每一个数的和 输入输出格式 输入格式: 第一行包含两个整数N.M,分别 ...

  7. Luogu3960 NOIP2017列队(splay/线段树)

    令splay中的一个点表示一段区间,需要使用其中某个点时将区间分裂即可,剩下的都是splay的基本操作了.写的非常丑陋,注意细节.感觉考场上肯定只能靠部分分苟活了.想起来去年因为各种莫名其妙的原因50 ...

  8. luogu P3285 [SCOI2014]方伯伯的OJ splay 线段树

    LINK:方伯伯的OJ 一道稍有质量的线段树题目.不写LCT splay这辈子是不会单独写的 真的! 喜闻乐见的是 题目迷惑选手 \(op==1\) 查改用户在序列中的位置 题目压根没说位置啊 只有排 ...

  9. 2018.11.01 loj#2319. 「NOIP2017」列队(线段树)

    传送门 唉突然回忆起去年去noipnoipnoip提高组试水然后省二滚粗的悲惨经历... 往事不堪回首. 所以说考场上真的有debuffdebuffdebuff啊!!!虽然当时我也不会权值线段树 这道 ...

随机推荐

  1. C++官方文档-this

    #include <iostream> using namespace std; class Dummy { public: int x; Dummy() : x() { } ; Dumm ...

  2. filzilla

    之前找了一套支援 SFTP (FTP over SSH) 的 FTP Server 就是為了解決 Port 不夠用的問題,直到最近才發現我們常用的 FileZilla Server 原來就有支援 FT ...

  3. hammer使用: 代码:捏合、捏开、图片放大 的一个手机图片“放大缩小可拖动”的小效果

    hammer.js 的使用. (手机手势插件) 捏合.捏开.图片放大 的一个手机图片“放大缩小可拖动”的小效果. 相关js 到 http://www.bootcdn.cn/  查找和下载. hamme ...

  4. leetcode342

    public class Solution { public bool IsPowerOfFour(int num) { ) && ((num & (num - )) == ) ...

  5. TTreeView.OnCustomDrawItem

    TTreeNode *node; node = , "AAAA"); TreeView1->Items->AddChild(node, "aaa1" ...

  6. 前端-CSS-9-文本和字体-背景颜色

    字体属性 div{ width: 300px; height: 100px; /*background-color: red;*/ border: 1px solid red; /*设置字体大小 px ...

  7. inline和inline-block的间隙问题

    我们在前端布局的时候,会偶尔发现,在具有inline/inline-block属性的元素间存在一小段间隙,网上有些文章说这个间隙是6px,但我觉得应该是一个空格的宽度. 这里以inline-block ...

  8. sublime打开文本时会记忆上次关闭时鼠标停留的位置

    sublime打开文本时会记忆上次关闭时鼠标停留的位置

  9. 本地Facts

    我们可以通过Facts来获取目标主机的系统信息,当这些信息还不能满足我们的功能需要时,可以通过编写自定义的Facts模块来实现.当然,还有一个更简单的实现方法,就是通过本地Facts来实现.只需在目标 ...

  10. Haskell语言学习笔记(38)Lens(1)

    Lens Lens是一个接近语言级别的库,使用它可以方便的读取,设置,修改一个大的数据结构中某一部分的值. view, over, set Prelude> :m +Control.Lens P ...