6.29 $\text{Data Structures}$
作者:互联网
\(\text{Date: 6.29}\)
\(\text{Summary - Better Data Structures}\)
\(\text{Contents: Segment Tree, (advanced)B-I-T}\)
\(\text{I - Segment Tree}\)
一些信息之所以能用线段树维护,是因为这些信息具有合并性(已知\([l,mid]\)和\([mid+1,r]\)的信息,能否整合推导出\([l,r]\)的信息),即可以被pushup
。题目要求区间修改时需考虑是否可以及如何设置懒标记tag[p]
。
细节与注意事项:(以Luogu P1471
为例)
\(1.\) 打好\(\#\rm define\)减码量
#include <bits/stdc++.h>
#define _ read()
#define LL long long
#define lsp p << 1
#define rsp p << 1 | 1
#define MID mid = (t[p].l + t[p].r) >> 1
#define lowbit(x) (x & -x)
using namespace std;
const int N = 1.2 * 1e6 + 5;
\(2.\) pushup较复杂时建议用结构体传递参数(或者直接 T operator + ... const
!)
inline T pushup(T L, T R) {
T res;
res.l = L.l, res.r = R.r;
res.sum = L.sum + R.sum;
res.val = L.val + R.val;
return res;
}
// pushup like this: (if you don't like it, just #define)
// t[p] = pushup(t[lsp], t[rsp]);
\(3.\) pushdown记得分步写(将更新区间和传递tag的操作分开),更简洁(F(x)
在update
中也能用到)
inline void Fx(int p, double d) { // 用参数 d (懒标记)更新节点 p
double Sum = t[p].sum; // 注意变量覆盖问题
t[p].sum += d * Len(p);
t[p].val += 2.0 * d * Sum + d * d * Len(p);
}
inline void pushdown(int p) {
Fx(lsp, tag[p]);
Fx(rsp, tag[p]);
tag[lsp] += tag[p];
tag[rsp] += tag[p];
tag[p] = 0.0;
}
\(4.\) query时将函数类型设为结构体类型,省去不必要的麻烦
T query(int p, int l, int r) {
if(l <= t[p].l && t[p].r <= r) return t[p];
pushdown(p); int MID;
if(r <= mid) return query(lsp, l ,r);
else if(l > mid) return query(rsp, l, r);
else return pushup(query(lsp, l, r), query(rsp, l, r));
}
Links
\(\large\to\text{A better B-I-T}\leftarrow\)
A Link
\(\rm Ex.\) 哎哟,这树状数组针不戳: \(\text{advanced B-I-T}\)
树状数组维护不可差分信息
使用树状数组在单步操作\(O(\log^2n)\)的时间复杂度内维护单点修改 + 区间最值查询。(感觉没有那么那么的实用,但是码量是真\(^{\rm TM}\)的少啊)
然而,下面这个东西就是非常强大了:
平衡树 \(\overset{\text{低 配 版}}{\leftarrow}\) 树状数组
限制:因为树状数组建立在值域上,而离散化会导致整个算法变为离线,所以遇到什么值域为\(10^9\)或有负数的树状数组就挂了。
update: 其实负数并不会挂,将整体值域+N
即可。
还有,平衡树版的树状数组无法实现反转。
代码仍然是真\(^{\rm TM}\)的短。
总代码:
#include <bits/stdc++.h>
#define _ read()
#define lowbit(x) (x & -x)
using namespace std;
const int N = 1e7 + 5;
inline void File() {
freopen("in.txt", "r", stdin);
freopen("Ans.txt", "w", stdout);
}
inline int read() {
int x = 0, w = 0; char ch = getchar();
while(!isdigit(ch)) { w |= ch == 45; ch = getchar(); }
while(isdigit(ch)) { x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar(); }
return w ? -x : x;
}
namespace Ax { // the common template
int n, a[N], c[N];
inline void update(int x, int d) {
for(int i = x; i <= n; i += lowbit(i)) c[i] += d;
}
inline int query(int x) {
int res = 0;
for(int i = x; i; i -= lowbit(i)) res += c[i];
return res;
}
// You can also use the B-I-T as a BUCKET.
}
namespace Bx { // 单点修改 + 区间最值 单次O(log^2 n)
int n, a[N], mx[N];
inline void update(int x, int d) { // 必须同时更新a[i]的值
a[x] = d;
for(int i = x; i <= n; i += lowbit(i)) {
mx[i] = a[i]; int t = lowbit(i);
for(int j = 1; j < t; j <<= 1) mx[i] = max(mx[i], mx[i - j]);
}
}
inline int query(int l, int r) { // 必须同时更新a[i]的值
int Len = r - l + 1, res = a[r];
while(Len && r) {
if(Len >= lowbit(r)) {
res = max(res, mx[r]);
Len -= lowbit(r);
r -= lowbit(r);
}
else {
res = max(res, a[r]);
Len--, r--;
}
}
return res;
}
/*
int g;
if(opt == 1) {
int x = _, y = _;
g = a[x];
update(x, y);
}
*/
}
namespace Cx { // BIT as a Treap
int n, a[N], c[N];
int Mx, MxLog;
inline void init() {
for(int i = 1; i <= n; i++) Mx = max(Mx, a[i]);
MxLog = log2(Mx) + 1;
}
inline void update(int x, int d) {
for(int i = x; i <= n; i += lowbit(i)) c[i] += d;
}
inline int query(int x) {
int res = 0;
for(int i = x; i; i -= lowbit(i)) res += c[i];
return res;
}
inline void Ins(int x) {
update(x, 1);
}
inline void Del(int x) {
update(x, -1);
}
inline int Rank(int x) {
return query(x - 1) + 1;
}
int kth(int k) {
int ans = 0, cnt = 0;
for(int i = 30; i >= 0; i--) {//i实际上为log2(maxn),maxn是a数组中的最大值
ans += (1 << i);
if(ans > Mx || cnt + c[ans] >= k) ans -= (1 << i);//>=k是为了防止有重复元素
else cnt += c[ans];
}
return ans + 1;//如果找不到则返回n+1
}
inline int pre(int x) {
return kth(Rank(x) - 1);
}
inline int nxt(int x) {
return kth(Rank(x + 1));
}
}
using namespace Cx;
signed main() {
File();
Mx = n = 1e7 + 5;
//MxLog = log2(Mx) + 1;
int T, opt, x;
for(T = read(); T; --T) {
opt = read(); x = read();
if(opt == 1) Ins(x), Mx = max(Mx, x);
else if(opt == 2) Del(x);
else if(opt == 3) printf("%d\n", Rank(x));
else if(opt == 4) printf("%d\n", kth(x));
else if(opt == 5) {
printf("%d\n", pre(x));
}
else {
printf("%d\n", nxt(x));
}
}
return 0;
}
\(\rm II - Splay\)
这个东西真的很难调!细节非常的多,操作也非常的灵活。
但是,可以总结几个有概率出错的地方:
\(1.\rm rotate\)
void rotate(int x) {
int f = t[x].fa, ff = t[f].fa, chk = get(x), w = t[x].ch[chk ^ 1];
t[f].ch[chk] = w; t[w].fa = f; // deal with ch[x][chk ^ 1]
t[ff].ch[get(f)] = x; t[x].fa = ff; // deal with fa[fa[x]]
t[x].ch[chk ^ 1] = f; t[f].fa = x; // between x & fa[x]
maintain(f), maintain(x);
}
\(2.\rm Splay\)
void splay(int x, int g = 0) {
while(t[x].fa != g) {
int f = t[x].fa, ff = t[f].fa;
if(ff != g) rotate(get(x) == get(f) ? f : x);
rotate(x);
}
if(!g) rt = x;
}
\(3.\) pre(x)
和 nxt(x)
记得加:
if(t[rt].val < v) // in pre
if(t[rt].val > v) // in nxt
注意 \(\text{kth(x), pre(x), nxt(x)}\) 返回的是节点编号。
然后就是一个查找特定节点的或特定区间的技巧:
int L = pre(v), R = nxt(v);
splay(L); splay(R, L);
int x = t[R].ch[0];
//或者
int L = kth(l), r = kth(r + 2);
splay(L); splay(R, L);
// t[R].ch[0] -> The interval of [l, r]
这样,\(rt=L,ch[rt][1]=R,ch[ch[rt][1]][0]\) 就是我们想要进行操作的位置。
在处理区间问题时,要多写一个 build(int l, int r, int Fa)
。在建立新节点的时候(在build
中或者是ins
中)还有写rotate
一定要特别小心。
(好像在写区间操作的时候,大家一般习惯\([1,n]\to[2,n+1]\)?)
还有在处理带tag
的问题时一定要及时maintain
和pushdown
!
\(\rm Splay\) 中常用函数的功能:
- \(\text{maintain(x), get(x), rotate(x), splay(x, g)}\) :基本函数,\(\rm splay\)的精髓
- \(\rm find(v)\) :辅助函数,将 \(v\) 转到根。当然若树中没有值 \(v\) 这个函数就会将 \(v\) 的前驱或后继转到根...注意判断。
- \(\text{ins(v), kth(v), pre(v), nxt(v), del(v)}\) :平衡树常规操作
- \(\text{build(l, r, Fa), pushdown(x), Init(l, r)}\) :区间操作时会用到(
Init(l, r)
可以将区间\([l,r]\)转到你想要的位置)。
标签:ch,Data,int,text,fa,res,rm,Structures 来源: https://www.cnblogs.com/Doge297778/p/16422961.html