Luogu P4810 / COCI2014-2015#3 Solution / rope 容器的使用
题面
初始存在一个编号为 \(0\) 的空栈。
除编号为 \(0\) 的站之外,维护 \(n\) 个栈,进行 \(n\) 次操作,第 \(i\) 次操作选择一个编号小于 \(i\) 的栈复制至第 \(i\) 个栈,支持将 \(i\) 加入栈顶/查询栈顶元素并弹出栈顶/查询多少个数同时存在于该栈和另一个指定的栈。
数据范围:\(n\le 3\times 10^5\)
原题面在此。
思路
由于每次加入的数均是唯一的,所以不同栈出现相同数的唯一方法就是继承同一个栈。
将不同的栈抽象成一棵树,每一个栈就是这棵树上的一个节点。如果继承后需要加入一个数,那么在被继承栈的节点下增加一个节点,将这个栈挂在这个节点上;如果需要弹出栈顶,就输出被继承栈所在节点代表的数字,然后将栈挂在这个节点的父节点上;如果需要查询出现相同数的个数,那么找到这两个栈所在节点的 LCA,输出这个节点的深度即可。
使用离线树剖/在线倍增均可解决,时间复杂度 \(O(n\log n)\)。
但是,为了更快地解出这道题,我们并不需要手打树剖或倍增。
GNU C++ rope
rope
为 GNU 提供的 C++ 扩展库内的一种数据结构,其表现为可持久化数组,内部实现为平衡树,支持 \(O(1)\) 的数组复制,以及 \(O(\log n)\) 的下标查询/任意位置插入/任意位置删除等操作。不支持修改操作。
rope
包含在 ext/rope
头文件以及 __gnu_cxx
命名空间中,由于 CCF 系列赛采用 g++
编译器,故可以使用。
所以,我们可以借助这个容器方便地实现题目中提到的 \(4\) 种操作。
- 栈复制
rp[i] = rp[x];
- 入栈
rp[i].push_back(i);
- 出栈
rp[i].pop_back();
- 查询 LCA
while (l <= r)
{
mid = (l + r) >> 1;
if (rp[i][mid - 1] == rp[y][mid - 1])
rs = mid, l = mid + 1;
else
r = mid - 1;
}
上方提到的 LCA 的本质其实是将栈从底到顶转化为数组后使得两数组对应值不同的第一个下标(以 \(0\) 开始)。
使用二分查询即可。
由于 rope
的时间复杂度带一个 \(\log\),所以总时间复杂度为 \(O(n\log^2 n)\)。
完整代码
#include <iostream>
#include <ext/rope>
using namespace std;
const int N = 3e5 + 10;
using namespace __gnu_cxx;
int n, l, r, mid, rs;
char ch;
rope<int> rp[N];
int main()
{
scanf("%d", &n);
for (int i = 1, x, y; i <= n; i++)
{
cin >> ch >> x;
rp[i] = rp[x];
if (ch == 'a')
{
rp[i].push_back(i);
continue;
}
if (ch == 'b')
{
printf("%d\n", rp[i].back());
rp[i].pop_back();
continue;
}
cin >> y;
l = 1, r = min(rp[i].size(), rp[y].size()), rs = 1;
while (l <= r)
{
mid = (l + r) >> 1;
if (rp[i][mid - 1] == rp[y][mid - 1])
rs = mid, l = mid + 1;
else
r = mid - 1;
}
printf("%d\n", !r or rp[i][0] != rp[y][0] ? 0 : rs);
}
}