P6272 [湖北省队互测2014] 没有人的算术 题解
本文参考了 湖北省队互测 Week1 解题报告,在部分之处说明可能不如原题解,如有错误请指出。
洛谷上的题面缺失了特殊性质,不过原题的特殊性质还是比较具有启发性的,下面是原题面中的数据范围。
测试点 \(1\)
考察选手的读题能力。按照题目提供的比较方式暴力递归即可。
测试点 \(2 \sim 3\)
考虑特殊性质随机生成的实质。注意到如果我们一直操作 C 1 1 1
的话,比较树的大小是 \(O(2^n)\) 的。但是在随机数据下,一个数字并不会被合并很多次,所以可以忽略比较的常数,直接用线段树维护序列,时间复杂度为 \(O(m \log n)\)。
测试点 \(4 \sim 5\)
注意到暴力维护的瓶颈实质上在于比较两个数的复杂度,例如在上个测试点中我们就是依赖 随机数据下两个数的比较复杂度低 来暴力解决的,那么如果规定了总共出现过的不同的数字不会超过 \(1000\) 个,那么不妨采用记忆化的思想,设 \(cmp_{i, j}\) 表示第 \(i\) 个出现的数字和第 \(j\) 个出现的数字比较的结果,暴力把这张 \(V^2\) 的表打出来,就能把比较的复杂度降为 \(O(1)\)。
依然采用线段树实现,复杂度为 \(O(V^2 + m\log n)\)。
测试点 \(6 \sim 7\)
仔细想想上面的做法在某种程度上没什么必要,如果我们能从小到大直接维护出数字的相对顺序,也能直接比较两个数。
考虑用平衡树维护这个序列(大小关系的序列),一件很好的事情是,我们的两个关键字都是已经出现过的值,那么只要根据 \(x_L, x_R\) 的排名就能在 \(O(\log V)\) 的时间内比较一对数。
这样比较一次是 \(O(\log V)\),那么插入一个数就是 \(O(\log^2 V)\) 的(插入路径上的所有点都需要查排名)。查询也就变成 \(O(\log n \log V)\) 的,那么复杂度为 \(O(m(\log^2 V + \log n\log V))\)。
测试点 \(8 \sim 10\)
上面所有的铺垫都在将答案指向一个地方——离散化。例如 \(50\) 分时,我们的离散化工具是 \(cmp_{i,j}\);在 \(70\) 分时,我们的离散化工具是两个数的排名。
有没有什么能够 \(O(1)\) 实现两个数比较的离散化方法呢?如果我们使用 精度足够 的实数,似乎也能实现这一想法!
于是我们直接考虑在 BST 上从根开始给每个节点赋值,例如从根开始是 \([0, V]\),然后分裂为 \([0, \dfrac{V}{2}),(\dfrac{V}{2}, V]\),以此类推。问题到这里还没结束,注意我们上文提到了如果想用实数进行比较,一定要保证 精度,于是我们想起平衡树来。
平衡树的树高是 \(\log n\) 级别,那么最底层对精度的要求为 \(2^{-\log n} = \dfrac{1}{n}\) ,而 \(n\) 只有 \(10^5\) 而已,所以精度是完全够用的。
但是,注意平衡树用于维护平衡的操作——旋转。麻烦的是,在进行一次旋转后,我们子树内所有点的值域都被完全打乱了,这个时候再插入数时每个点的值域就完全不一样了,所以考虑用结构稳定,依赖定期重构维护平衡结构的重量平衡树,这样我们可以在每次重构的过程中顺便计算每个点维护的值域。
大功告成,复杂度即为 \(O(m(\log V + \log n))\)。实现用了替罪羊树。
// 如果命运对你缄默, 那就活给他看。
// #pragma GCC optimize(1)
// #pragma GCC optimize(2)
// #pragma GCC optimize(3)
// #pragma GCC optimize("Ofast", "inline", "-ffast-math")
// #pragma GCC target("avx,sse2,sse3,sse4,mmx")
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
// #define int LL
const int maxn = 600010;
int w[maxn], n, m;
namespace scapegt {
int tot, rt;
struct Node {
int x, y;
double v;
int ls, rs;
int c, sz;
} t[maxn];
const double ap = 0.75;
inline void pu(int u) {
t[u].sz = t[t[u].ls].sz + t[t[u].rs].sz + 1;
}
inline bool balance(int u) {
double lw = t[u].sz * ap;
return max(t[t[u].ls].sz, t[t[u].rs].sz) <= lw;
}
int acc = 0;
int v[maxn];
inline void dfs1(int u) {
if(!u) return ;
dfs1(t[u].ls);
v[++ acc] = u;
dfs1(t[u].rs);
}
inline int rebuild(int l, int r, double vl, double vr) {
if(l > r) return 0;
int mid = l + r >> 1;
double vmid = (vl + vr) / 2;
int u = v[mid];
t[u].v = vmid;
t[u].ls = rebuild(l, mid - 1, vl, vmid);
t[u].rs = rebuild(mid + 1, r, vmid, vr);
pu(u);
return u;
}
inline void dfs(int& u, double vl, double vr) {
acc = 0;
dfs1(u);
u = rebuild(1, acc, vl, vr);
}
inline void insert(int& u, double vl, double vr, int l, int r, int k) {
double mid = (vl + vr) / 2;
if(!u) {
u = ++ tot;
t[u].x = w[l], t[u].y = w[r];
t[u].v = mid;
t[u].c = t[u].sz = 1;
w[k] = u;
return ;
}
if(t[t[u].x].v == t[w[l]].v && t[t[u].y].v == t[w[r]].v) {
w[k] = u;
t[u].c ++, pu(u);
return ;
}
if(t[w[l]].v < t[t[u].x].v || (t[w[l]].v == t[t[u].x].v && t[w[r]].v < t[t[u].y].v)) insert(t[u].ls, vl, mid, l, r, k);
else insert(t[u].rs, mid, vr, l, r, k);
pu(u);
if(!balance(u)) dfs(u, vl, vr);
}
inline void print(int u) {
if(!u) return ;
print(t[u].ls);
cout << fixed << setprecision(3) << u << ' ' << t[u].v << ' ' << t[u].x << ' ' << t[u].y << " " << '\n';
print(t[u].rs);
}
}
namespace sgt {
int mx[maxn];
inline int cmx(int x, int y) {
return scapegt :: t[w[x]].v >= scapegt :: t[w[y]].v ? x : y;
}
inline void pu(int u) {
mx[u] = cmx(mx[u << 1], mx[u << 1 | 1]);
}
inline void modf(int u, int l, int r, int p) {
if(l == r) return ;
int mid = l + r >> 1;
if(p <= mid) modf(u << 1, l, mid, p);
else modf(u << 1 | 1, mid + 1, r, p);
pu(u);
}
inline int Q(int u, int l, int r, int ql, int qr) {
if(ql <= l && r <= qr) return mx[u];
int mid = l + r >> 1;
if(qr <= mid) return Q(u << 1, l, mid, ql, qr);
if(ql > mid) return Q(u << 1 | 1, mid + 1, r, ql, qr);
return cmx(Q(u << 1, l, mid, ql, qr), Q(u << 1 | 1, mid + 1, r, ql, qr));
}
inline void build(int u, int l, int r) {
mx[u] = l;
if(l == r) return ;
int mid = l + r >> 1;
build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
}
}
signed main() {
ios :: sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n >> m;
char c[2];
sgt :: build(1, 1, n);
for(int i = 1; i <= m; ++ i) {
int l, r, k;
cin >> c >> l >> r;
if(*c == 'C') {
cin >> k;
scapegt :: insert(scapegt :: rt, 1.0, 1e9, l, r, k);
sgt :: modf(1, 1, n, k);
} else {
cout << sgt :: Q(1, 1, n, l, r) << '\n';
}
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!