题目链接

http://acm.hdu.edu.cn/showproblem.php?pid=6873

题目大意

给定一组 \(n\) 列的方块,每列方块数 \(b_i\) ,现有 \(q\) 次操作/询问。

若 \(O = 1\) ,则将第 \(x\) 列第 \(y\) 个及其以上的方块向左推1格, \(x\) 列左侧部分方块也随之运动(如果存在,且能推动),并回答推动的方块总数目。

若 \(O = 2\) ,则回答第 \(x\) 列当前的方块数目。

注意:方块会受重力下落。

题目解析





使用 \(Splay\) 维护方块序列,时间复杂度:建树 \(O(n \log n)\) 、查询、删除和插入均为 \(O(\log n)\) ,因此总复杂度为 \(O((n+q) \log n)\) ,可以通过该题。

具体参见代码。

参考代码

#include <bits/stdc++.h>
#define N 400005
#define EMPTY 0
#define INF INT_MAX
#define ll long long
using namespace std;
struct Tree{
int data, dataMin, dataMax, size, fa, child[2];
ll dataSum;
} t[N]; //其中data, fa, child为节点的基本属性
int cnt, root;
vector <int> dataset, nodeBin; inline void read(int &s) { //快读,支持int
s = 0;
int tt = 1, k = getchar();
for (; k < '0' || k > '9'; k = getchar()) if (k == '-') tt = -1;//判断该数正负
for (; k >= '0' && k <= '9'; k = getchar()) s = s * 10+(k ^ 48);//^48相当于-‘0’,较快。
s *= tt;
}
inline void write(ll s) { //快写,支持int和long long
int tt = 0, a[40];
if (s < 0) putchar('-'), s = -s;
do { a[++tt] = s % 10; } while (s /= 10);//用do while就不用特判一个0
while(tt) putchar(48+a[tt--]);
}
inline int checkSize(int x) { return (x == EMPTY) ? 0 : t[x].size;}
inline int checkDataMin(int x) { return (x == EMPTY) ? INF : t[x].dataMin;}
inline int checkDataMax(int x) { return (x == EMPTY) ? -INF : t[x].dataMax;}
inline ll checkDataSum(int x) { return (x == EMPTY) ? 0 : t[x].dataSum;}
inline void updNode(int x) {
t[x].size = checkSize(t[x].child[0]) + checkSize(t[x].child[1]) + 1;
t[x].dataMin = min(t[x].data, min(checkDataMin(t[x].child[0]), checkDataMin(t[x].child[1])));
t[x].dataMax = max(t[x].data, max(checkDataMax(t[x].child[0]), checkDataMax(t[x].child[1])));
t[x].dataSum = checkDataSum(t[x].child[0]) + checkDataSum(t[x].child[1]) + t[x].data;
}
void rotate(int x, int o)
{
int y = t[x].fa;
if (!y) return;
int z = t[y].fa;
t[y].child[o^1] = t[x].child[o];
if (t[x].child[o] != EMPTY) t[t[x].child[o]].fa = y;
t[x].fa = z;
if (z != EMPTY)
{
if (t[z].child[0] == y) t[z].child[0] = x;
else t[z].child[1] = x;
}
t[x].child[o] = y;
t[y].fa = x;
updNode(y);
updNode(x);
}
void splay(int x)
{
if (x == EMPTY) return;
int y;
while (t[x].fa != EMPTY)
{
y = t[x].fa;
if (t[y].fa == EMPTY) //旋转后为根节点
{
if (t[y].child[0] == x) rotate(x, 1);
else rotate(x, 0);
break;
}
else {
if (t[t[y].fa].child[1] == y)
{
if (t[y].child[0] == x) rotate(x, 1), rotate(x, 0);
else rotate(y, 0), rotate(x, 0);
}
else {
if (t[y].child[1] == x) rotate(x, 0), rotate(x, 1);
else rotate(y, 1), rotate(x, 1);
}
}
}
root = x;
} inline int mininum(int x) { //找x的子树中序号最小的
while (t[x].child[0] != EMPTY) x = t[x].child[0];
return x;
}
inline int maxinum(int x) { //找x的子树中序号最大的
while (t[x].child[1] != EMPTY) x = t[x].child[1];
return x;
}
inline int succ(int x) { //找x的后继
splay(x);
if (t[x].child[1] == EMPTY) return EMPTY;
return mininum(t[x].child[1]);
}
inline int prec(int x) { //找x的前驱
splay(x);
if (t[x].child[0] == EMPTY) return EMPTY;
return maxinum(t[x].child[0]);
}
int createNode(int data) //新建节点,存放data(优先取用废弃内存池)
{
if (nodeBin.empty())
{
t[++cnt] = (Tree){data, data, data, 1, EMPTY, EMPTY, EMPTY, data};
return cnt;
}
int x = nodeBin.back();
t[x] = (Tree){data, data, data, 1, EMPTY, EMPTY, EMPTY, data};
nodeBin.pop_back();
return x;
}
int findKth(int x, int k) //找序号为k的节点
{
while (true)
{
if (x == EMPTY) return EMPTY;
int lc = checkSize(t[x].child[0]);
if (k <= lc) x = t[x].child[0];
else {
if (k == lc+1) return x;
else {x = t[x].child[1], k -= lc+1;}
}
}
}
inline int getKth(int x) { //找节点x的序号k
splay(x);
return checkSize(t[x].child[0]) + 1;
}
void insertKth(int x, int k) //将单节点x插入树中的序号k的位置
{
if (!root) {root = x; return;}
if (k <= 0 || k > t[root].size+1) return;
if (k == 1) {
int y = mininum(root);
if (y == EMPTY) return;
splay(y);
t[y].child[0] = x;
t[x].fa = y;
updNode(y);
return;
}
int y = findKth(root, k-1);
if (y == EMPTY) return;
splay(y);
t[x].child[1] = t[y].child[1];
if (t[y].child[1] != EMPTY) t[t[y].child[1]].fa = x;
t[y].child[1] = EMPTY;
t[x].child[0] = y;
t[y].fa= x;
root = x;
updNode(y);
updNode(x);
}
void deleteKth(int k) //删除树上序号为k的节点
{
if (!root) {return;}
if (k <= 0 || k > t[root].size) return;
if (k == 1) {
int y = mininum(root);
if (y == EMPTY) return;
nodeBin.push_back(y);
splay(y);
if (t[y].child[1] != EMPTY) t[t[y].child[1]].fa = EMPTY, root = t[y].child[1];
else root = 0;
return;
}
int y = findKth(root, k);
if (y == EMPTY) return;
splay(y);
nodeBin.push_back(y);
int z = prec(y);
t[z].child[1] = t[y].child[1];
if (t[y].child[1] != EMPTY) t[t[y].child[1]].fa = z;
t[t[y].child[0]].fa = EMPTY;
root = t[y].child[0];
while (z != EMPTY)
{
updNode(z);
z = t[z].fa;
}
}
int buildTree(int L, int R, int fa) //建树,data取用dataset[L]~[R],L>0
{
if (L > R) return EMPTY;
if (L == R) {
t[L] = (Tree){dataset[L], dataset[L], dataset[L], 1, fa, EMPTY, EMPTY, dataset[L]};
return L;
}
int mid = (L+R)/2;
t[mid] = (Tree){dataset[mid], 0, 0, 0, fa, EMPTY, EMPTY, 0};
t[mid].child[0] = buildTree(L, mid-1, mid);
t[mid].child[1] = buildTree(mid+1, R, mid);
updNode(mid);
return mid;
}
inline void clearAll() { //清空全部
cnt = 0;
dataset.clear();
nodeBin.clear();
root = 0;
}
int findData(int x, int data) //找到x的子树上<=Data的最大序号的节点
{
int a = EMPTY;
if (x == EMPTY) return EMPTY;
if (data < t[x].dataMin) return EMPTY;
if (t[x].child[1] != EMPTY) {
if (data >= checkDataMin(t[x].child[1])) {
a = findData(t[x].child[1], data);
}
}
if (a != EMPTY) return a;
if (t[x].data <= data) return x;
if (t[x].child[0] != EMPTY) {
if (data >= checkDataMin(t[x].child[0])) {
a = findData(t[x].child[0], data);
}
}
if (a != EMPTY) return a;
return EMPTY;
}
inline void printTree() { //将树上每个节点data按序输出
int x = mininum(root);
while (x != EMPTY) {
write(t[x].data);
x = succ(x);
if (x != EMPTY) putchar(' ');
}
putchar('\n');
}
int main()
{
int T, n, q;
read(T);
while (T--) {
clearAll();
read(n), read(q);
dataset.push_back(0);
for (int i = 0; i < n; ++i) {
int x;
read(x);
dataset.push_back(x);
}
root = buildTree(1, n, EMPTY);
while (q--)
{
int o, x, y, now;
read(o);
if (o == 1)
{
read(x), read(y);
now = findKth(root, x);
if (t[now].data < y) {putchar(48), putchar('\n'); continue;}
splay(now);
int p = findData(t[now].child[0], y-1);
if (p == EMPTY) {putchar(48), putchar('\n'); continue;}
ll tot = checkDataSum(t[now].child[0]) + t[now].data;
int r = succ(p);
if (r != EMPTY)
{
splay(r);
tot -= checkDataSum(t[r].child[0]);
t[r].data -= y-1 - t[p].data;
}
tot -= (ll)(getKth(now) - getKth(p))*(y-1);
deleteKth(getKth(p));
insertKth(createNode(y-1), x);
write(tot);
putchar('\n');
}
else {
read(x);
now = findKth(root, x);
if (now != EMPTY) write(t[now].data);
else putchar(48);
putchar('\n');
}
}
printTree();
}
return 0;
}

感谢支持!

Splay(伸展树)/HDU6873的更多相关文章

  1. Splay伸展树学习笔记

    Splay伸展树 有篇Splay入门必看文章 —— CSDN链接 经典引文 空间效率:O(n) 时间效率:O(log n)插入.查找.删除 创造者:Daniel Sleator 和 Robert Ta ...

  2. 【学时总结】◆学时·VI◆ SPLAY伸展树

    ◆学时·VI◆ SPLAY伸展树 平衡树之多,学之不尽也…… ◇算法概述 二叉排序树的一种,自动平衡,由 Tarjan 提出并实现.得名于特有的 Splay 操作. Splay操作:将节点u通过单旋. ...

  3. Splay 伸展树

    废话不说,有篇论文可供参考:杨思雨:<伸展树的基本操作与应用> Splay的好处可以快速分裂和合并. ===============================14.07.26更新== ...

  4. [Splay伸展树]splay树入门级教程

    首先声明,本教程的对象是完全没有接触过splay的OIer,大牛请右上角.. 首先引入一下splay的概念,他的中文名是伸展树,意思差不多就是可以随意翻转的二叉树 PS:百度百科中伸展树读作:BoGa ...

  5. Splay伸展树入门(单点操作,区间维护)附例题模板

    Pps:终于学会了伸展树的区间操作,做一个完整的总结,总结一下自己的伸展树的单点操作和区间维护,顺便给未来的自己总结复习用. splay是一种平衡树,[平均]操作复杂度O(nlogn).首先平衡树先是 ...

  6. Codeforces 675D Tree Construction Splay伸展树

    链接:https://codeforces.com/problemset/problem/675/D 题意: 给一个二叉搜索树,一开始为空,不断插入数字,每次插入之后,询问他的父亲节点的权值 题解: ...

  7. UVA 11922 Permutation Transformer —— splay伸展树

    题意:根据m条指令改变排列1 2 3 4 … n ,每条指令(a, b)表示取出第a~b个元素,反转后添加到排列尾部 分析:用一个可分裂合并的序列来表示整个序列,截取一段可以用两次分裂一次合并实现,粘 ...

  8. [算法] 数据结构 splay(伸展树)解析

    前言 splay学了已经很久了,只不过一直没有总结,鸽了好久来写一篇总结. 先介绍 splay:亦称伸展树,为二叉搜索树的一种,部分操作能在 \(O( \log n)\) 内完成,如插入.查找.删除. ...

  9. ZOJ3765---Lights (Splay伸展树)

    Lights Time Limit: 8 Seconds      Memory Limit: 131072 KB Now you have N lights in a line. Don't wor ...

  10. codeforces 38G - Queue splay伸展树

    题目 https://codeforces.com/problemset/problem/38/G 题意: 一些人按顺序进入队列,每个人有两个属性,地位$A$和能力$C$ 每个人进入时都在队尾,并最多 ...

随机推荐

  1. [火星补锅] 水题大战Vol.2 T1 && luogu P1904 天际线 题解 (线段树)

    前言: 当时考场上并没有想出来...后来也是看了题解才明白 解析: 大家(除了我)都知道,奇点和偶点会成对出现,而出现的前提就是建筑的高度突然发生变化.(这个性质挺重要的,我之前没看出来) 所以就可以 ...

  2. c语言编程基础入门必备知识

    数据类型 基本数据类型 类型名称说明char字符类型存放字符的ASCII码int整型存放有符号整数short短整型存放有符号整数long长整型存放有符号整数long long存放有符号整数float单 ...

  3. 穿点最多的直线 牛客网 程序员面试金典 C++

    穿点最多的直线 牛客网 程序员面试金典 C++ 题目描述 在二维平面上,有一些点,请找出经过点数最多的那条线. 给定一个点集vectorp和点集的大小n,没有两个点的横坐标相等的情况,请返回一个vec ...

  4. Codeforces Round #744 (Div. 3) G题题解

    淦,最后一道题没写出来,...还是我太菜了,不过这个题确实比较有趣. G. Minimal Coverage 简化题意:就是你处在坐标轴的0点上,给你一个序列\(a_i\),每次你可以选择向左走\(a ...

  5. c#复制数组的多种方法

    方法一:使用for循环 int []pins = {9,3,7,2} int []copy = new int[pins.length]; for(int i =0;i!=copy.length;i+ ...

  6. mac bigsur 安装mysql步骤

    我首先下载的是mysql8.x,安装完后,在偏好设置里面,双击mysql图标,弹窗:未能载入偏好设置面板MySQL,重启无果,查攻略说是要安装5.7.x,在mysql官网上,下载5.7.29 强烈建议 ...

  7. shell 脚本控制命令的执行顺序

    &&,||,(),{},& 五个符号的运用shell脚本执行命令的时候,有时候会依赖于前一个命令是否执行成功.而&&和||就是用来判断前一个命令执行效果的. 也 ...

  8. Linux下软链接与硬链接的区别

    由于下面会说到inode,所以如果没有了解过,请务必搞懂inode的真正含义,厚颜无耻的推荐我的一篇博客:Linux磁盘与文件系统管理 如果我们在系统中新建一个文件,我们看到的文件名实际上只是表面现象 ...

  9. jenkins项目发布

    目录 一.简介 二.docker打包 一.后端打包 二.前端打包 三.启动容器 四.完整代码 五.发布测试 六.优化方案 七.源码地址: 八.参考 一.简介 1.该章节基于jenkins.Harbor ...

  10. Json跨域登录问题的之Access-Control-Allow-Origin 站点跨域请求的问题

    跨域调用json问题 闲暇之时,做了一个博客站点,站点发布网络之后程序功能完成,最后发现了一个跨域的问题,比如我使用abc.com打开系统,一切正常,后台没有任何文件请求报错问题,然后我又使用了www ...