题目链接

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. Elasticsearch核心技术(三):Mapping设置

    本文从Mapping简介.Dynamic Mapping.自定义Mapping和Mapping常用参数说明4个部分介绍Elasticsearch如何设置Mapping. 3.1 Mapping简介 3 ...

  2. Linux C语言链表详细分析

    链表是一种常见的基础数据结构,结构体指针在这里得到了充分的利用.链表可以动态的进行存储分配,也就是说,链表是一个功能极为强大的数组,他可以在节点中定义多种数据类型,还可以根据需要随意增添,删除,插入节 ...

  3. 从零开始的DIY智能家居 - 基于 ESP32 的土壤湿度传感器

    前言 自从上次做了那个 甲醛传感器 和 水浊度传感器 之后开始尝到智能家居的甜头了,这两东西有没有用我不知道,但是没事的时候掏出手机瞄两眼,看着就让人很安心( ̄︶ ̄). 于是懒惰的我开始琢磨把给植物浇 ...

  4. 转移指令原理和Inline Hook

    目录 转移指令原理和Inline Hook 转移指令 操作符offset jmp指令 根据位移进行转移的jmp指令 插播HOOK知识 Inline Hook Inline Hook 原理 Hook代码 ...

  5. Appium 介绍与环境搭建

    目录 Appium 介绍 APP 自动化测试介绍 什么是 Appium ? Appium 优势 Appium 架构 Appium 生态 Appium 组件 UiAutomator API Bootst ...

  6. 释放 cached 内存

    巡检服务器发现内存可用很少了 top 命令查看是没有占用大内存的进程,cached特别大,释放cached就可以了 可用内存= free + buffers + cached 以下方法可以释放cach ...

  7. 安装RedHat和Centos后做的15件事情

    由于之前的Centos 7不支持无线网络连接,我尝试着将内核升级至4.8还是无效,遂决定换回RedHat 7,目前系统已经安装好,版本是Red Hat Enterprise Linux 7.3,下面是 ...

  8. docker中镜像的作用

    镜像原理镜像 镜像到底是什么?镜像是一种轻量级.可执行的独立软件包,用来打包软件运行环境和基于运行环境开发的软件,它包含运行某个软件所需的所有内容,包括代码.运行时.库.环境变量和配置文件.1.Uni ...

  9. CefSharp-基于C#的客户端开发框架技术栈开发全记录

    CefSharp简介 源于Google官方 CefSharp用途 CefSharp开发示例 CefSharp应用--弹窗与右键 不弹出子窗体 禁用右键 CefSharp应用--High DPI问题 缩 ...

  10. Javascript复制内容到剪贴板,解决navigator.clipboard Cannot read property 'writeText' of undefined

    起因 最近帮同事实现了一个小功能--复制文本到剪贴板,主要参考了前端大神阮一峰的博客,根据 navigator.clipboard 返回的 Clipboard 对象的方法 writeText() 写文 ...