权值数据结构水各种题
权值数据结构水各种题
前置知识
树状数组, 线段树, 分块... 反正任何你能想到的能求和的数据结构就行, 只要数据结构能单点加求区间和, 就能当权值数据结构.
给树状数组和线段树的链接吧, 分块现在没有, 以后大概率也没有 (莫队应该会有).
还有如果值域过大需要离散化或用动态开点线段树, 不过动态开点线段树等Defad下次介绍持久化线段树和主席树的时候再说.
什么是权值, 以及什么是权值数据结构
Defad对于权值的理解是一个值出现的次数 (和图论的边权不一样), 就是说一个值出现了几次.
权值数据结构就是维护权值的数据结构.
一个题简单理解权值数据结构
堆板子
插入就是插入的值的权值 \(+ 1\), 删除就是在第一个有权值的点 \(- 1\), 查询就是输出第一个有权值的点.
这里我们有一个巨大的优化, 线段树上二分 (Defad用线段树举例但同学们可以用任何的可以单点加求区间和的数据结构, 不过线段树上二分可以 \(O(\log N)\) (改变不了常数巨大的事实), 但别的数据结构都是 \(O(k \log N)\) 其中 \(k\) 是单次查询复杂度).
线段树上二分其实很简单, 左边满足条件就查左边, 否则查右边, 给一个查询 \(k\) 的下标的板子.
int kth(int x, int l, int r, int k) {
if (l == r) { // 递归到叶子了
return l; // l 和 r 相等, 随便返回一个就是下标
} else {
int m(l + r >> 1);
// 权值数据结构一般不用区间修改, 所以没有 pushdown
if (k <= val[x << 1]) // k 比左边的值小
return kth(x << 1, l, m, k); // 查左边
else if (k <= val[x]) // k 比当前点值小
return kth(x << 1 | 1, m + 1, r, k - val[x << 1]); // 扣掉左边再查右边
else // 这个其实可以不用, 但Defad习惯写
return -1;
}
}
所以说这个堆板子就比较容易写了.
因为Defad懒得写离散化, 这里只给权值加, 查询第 \(k\) 大, 删第 \(k\) 大, 调用的时候在 \(k\) 的参数位置写 \(1\) 就好了 (Tips: 离散化是离线的, 动态开点可以在线).
void chg(int x, int l, int r, int I, int k) { // 这里的 k 是加的权值, 一般是 1
if (l == r) { // 单点加
val[x] += k;
} else {
int m(l + r >> 1);
if (I <= m)
chg(x << 1, l, m, I, k);
else
chg(x << 1 | 1, m + 1, r, I, k);
val[x] = val[x << 1] + val[x << 1 | 1];
}
}
int kth(int x, int l, int r, int k) {
if (l == r) {
return l;
} else {
int m(l + r >> 1);
if (k <= val[x << 1])
return kth(x << 1, l, m, k);
else if (k <= val[x])
return kth(x << 1 | 1, m + 1, r, k - val[x << 1]);
else
return -1;
}
}
void del(int x, int l, int r, int k) {
if (l == r) {
val[x] -= 1;
} else {
int m(l + r >> 1);
if (k <= val[x << 1])
del(x << 1, l, m, k);
else
del(x << 1 | 1, m + 1, r, k - val[x << 1]);
val[x] = val[x << 1] + val[x << 1 | 1];
}
}
理解了权值数据结构, 那么我们来求逆序对吧
作为指针神教现任教主, 这题是Defad当年用指针线段树过的.
说一下怎么统计答案, 遍历数组, 加上当前值后统计i - qry(1, 1, N, 1, a[i])
(这里的qry
是求区间和), 用于统计的变量记得开long long
.
平衡树
刚才我们都打过堆板子了, 平衡树其实也可以用权值线段树水过去.
应该不用Defad再说怎么做每个操作了吧?
稍微放个简单题, 最大 \(M\) 宽区间和
亮度就是权值, 这个题好像没法离散化, 因为需要求所有宽为 \(M\) 的区间的和的最值.
郁闷的出纳员
Defad感觉这题细节挺多的, 就说一个最重要的, 权值平移操作.
权值线段树的权值不太方便平移, 所以可以用一个辅助变量 \(level\) 的加减实现平移操作, 此时的kth
的答案应该是 \(l + level\).