权值线段树、主席树
前言
拖了很久,就因为少看了一句话理解不了 T^T
权值线段树看上去名字高大上,其实就是以值为下标的线段树
主席树是可持久化权值线段树,但是后来我们就不理了,可持久化线段树全都写成主席树。所谓可持久化,就是保存了历史版本,所以这是黑科技呀!(⊙o⊙)
- 线段树:
电子科大 —— 算法讲堂 《权值线段树 主席树》
https://www.bilibili.com/video/av16552942?from=search&seid=7293261633840621022 - 主席树:
http://www.yhzq-blog.cc/%e4%b8%bb%e5%b8%ad%e6%a0%91%e5%ad%a6%e4%b9%a0%e6%80%bb%e7%bb%93/
https://www.cnblogs.com/zyf0163/p/4749042.html
一些思考
1.求任意区间不同数的和
其实十分像后缀数组,个人认为其思想是 “前缀的后缀可以表示任意子串”
Minimum Inversion Number - HDU - 1394
题意
求序列循环位移中逆序对数量最小值
思路
权值线段树
动态插入,这时比这个数大的数的数量即为新增的逆序对,累加后再处理循环位移的问题
循环位视为将最后一个数搬到最开始,答案的改变为 - (n – 末尾数 - 1) (即 末尾数 + 1 ~ n - 1) + (末尾数)(即 0 ~ 末尾数-1)
#include <cstdio>
#include <cstring>
#include <algorithm>
#define lson l, m, rt << 1
#define rson m + 1, r, rt << 1 | 1
using namespace std;
const int N = 5e3 + 15;
int a[N];
int sum[N << 2];
inline void init(){
memset(sum, 0, sizeof(sum));
}
void pushUp(int rt){ sum[rt] = sum[rt << 1] + sum[rt << 1 | 1]; }
void push(int val, int l, int r, int rt){
if(l == r){
sum[rt]++;
return;
}
int m = (l + r) >> 1;
if(val <= m) push(val, lson);
else push(val, rson);
pushUp(rt);
}
int query(int ql, int l, int r, int rt){
if(ql <= l){
return sum[rt];
}
int ans = 0;
int m = (l + r) >> 1;
if(ql <= m) ans += query(ql, lson);
ans += query(ql, rson); //右端点是n,故每次都查询右子树
return ans;
}
int main(){
int n;
while(~scanf("%d", &n)){
init();
for(int i = 0; i < n; i++){
scanf("%d", &a[i]);
a[i]++;
}
int ans_tmp = 0, ans = 0;
for(int i = 0; i < n; i++){
push(a[i], 1, n, 1);
ans_tmp += query(a[i] + 1, 1, n, 1);
}
ans = ans_tmp;
for(int i = n - 1; i > 0; i--){
ans_tmp = ans_tmp + (a[i]) - (n - a[i] – 1);
ans = min(ans, ans_tmp);
}
printf("%d\n", ans);
}
}
KPI - HDU - 5249
思路
离散化 + 权值线段树
数字过大,不能直接用下标,所以先离散化
因为离散化,只能转离线了
最后求中位数就是求第 sum/2 + 1 小数
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#define lson l, m, rt << 1
#define rson m + 1, r, rt << 1 | 1
using namespace std;
const int N = 1e4 + 15;
int sum[N << 2];
int op_num[N], op[N]; //op_num为操作数,op是操作
int mp[N];
int n, m;
queue<int> que;
inline void init(){
memset(sum, 0, sizeof(sum));
n = m = 0;
while(!que.empty()) que.pop();
}
void pushUp(int rt) { sum[rt] = sum[rt << 1] + sum[rt << 1 | 1]; }
void setVal(int p, int val, int l, int r, int rt){
if(l == r){
sum[rt] = val;
return;
}
int m = (l + r) >> 1;
if(p <= m) setVal(p, val, lson);
else setVal(p, val, rson);
pushUp(rt);
}
int query(int k, int l, int r, int rt){
if(l == r){
return l;
}
int m = (l + r) >> 1;
if(k <= sum[rt << 1]) return query(k, lson);
else return query(k - sum[rt << 1], rson);
}
int main(){
int q;
int csn = 1;
while(~scanf("%d", &q)){
init();
while(q--){
char strop[10];
int num;
scanf("%s", strop);
if(strop[0] == 'i'){ //insert操作对应0
scanf("%d", &num);
mp[m++] = num;
que.push(num);
op_num[n] = num;
op[n++] = 0;
}else if(strop[0] == 'q'){ //query操作对应2
op_num[n] = -1;
op[n++] = 2;
}else if(strop[0] == 'o'){ //out操作对应1
int tmp = que.front();
que.pop();
op_num[n] = tmp;
op[n++] = 1;
}
}
sort(mp, mp + m);
printf("Case #%d:\n", csn++);
for(int i = 0; i < n; i++){
int p;
if(op[i] == 0){
p = lower_bound(mp, mp + m, op_num[i]) - mp;
setVal(p + 1, 1, 1, m, 1); //插入即设置为1,求和
}else if(op[i] == 1){
p = lower_bound(mp, mp + m, op_num[i]) - mp;
setVal(p + 1, 0, 1, m, 1); //删除即设置为0,求和
}else{
p = sum[1]/2 + 1; //求中位数,即第sum[1]/2 + 1个数
int loc = query(p, 1, m, 1);
printf("%d\n", mp[loc - 1]);
}
}
}
}
Kth number - HDU - 2665
题意
求某个区间的第k小数
思路
主席树(可持续化权值线段树) + 离散化
#include <cstdio>
#include <algorithm>
#include <cstring>
#define lson l, m
#define rson m + 1, r
using namespace std;
const int N = 1e5 + 15;
int mp[N], a[N];
int tot;
int sum[N*20], root[N*20], ls[N*20], rs[N*20]; //ls为左孩子,rs为右孩子,root为每棵树的根
inline void init(){
mp[0] = a[0] = 0;
tot = 1;
}
void build(int& o, int l, int r){ //建立空树
o = tot++;
sum[o] = 0;
if(l == r) return;
int m = (l + r) >> 1;
build(ls[o], lson);
build(rs[o], rson);
}
void update(int& o, int pre, int l, int r, int p){ //只修改变化的子树,剩下相连
o = tot++;
ls[o] = ls[pre];
rs[o] = rs[pre];
sum[o] = sum[pre] + 1;
if(l == r) return;
int m = (l + r) >> 1;
if(p <= m) update(ls[o], ls[pre], lson, p);
else update(rs[o], rs[pre], rson, p);
}
int query(int ss, int tt, int l, int r, int k){
if(l == r) return l;
int m = (l + r) >> 1;
int lson_sum = sum[ls[tt]] - sum[ls[ss]]; //以差值建树
if(k <= lson_sum) query(ls[ss], ls[tt], lson, k);
else query(rs[ss], rs[tt], rson, k - lson_sum);
}
int main(){
int t;
scanf("%d", &t);
while(t--){
init();
int n, q;
scanf("%d%d", &n, &q);
for(int i = 1; i <= n; i++){
scanf("%d", &a[i]);
mp[i] = a[i];
}
sort(mp + 1, mp + n + 1);
int sz = unique(mp + 1, mp + n + 1) - (mp + 1);
build(root[0], 1, sz);
for(int i = 1; i <= n; i++){
int p = lower_bound(mp + 1, mp + sz + 1, a[i]) - mp;
update(root[i], root[i - 1], 1, sz, p); //从上一棵树建树
}
while(q--){
int l, r, k;
scanf("%d%d%d", &l, &r, &k);
int p = query(root[l - 1], root[r], 1, sz, k); //[1, r] – [1, l - 1] = [l, r]
printf("%d\n", mp[p]);
}
}
return 0;
}
Turing Tree - HDU - 3333
题意
求某个区间内不重复元素和
思路
主席树(可持续化线段树),也可以用莫队写
对区间[1, r]建树,每次插入数,当该数没出现过时直接插入,出现过时删掉上次的,再插入这一次的, 删除和插入操作都是新开树进行的,即历史版本之一
最后对于查询的[l, r],查询root[r]中的[l, r]区间即可,即前缀的后缀
#include <cstdio>
#include <algorithm>
#include <cstring>
#define lson l, m
#define rson m + 1, r
using namespace std;
const int N = 3e4 + 15;
typedef long long ll;
int pre_pos[N];
ll mp[N], a[N];
int tot;
ll sum[N*40];
int root[N*40], ls[N*40], rs[N*40];
inline void init(){
memset(pre_pos, 0, sizeof(pre_pos));
mp[0] = a[0] = 0;
tot = 1;
}
void build(int& o, int l, int r){
o = tot++;
sum[o] = 0;
if(l == r) return;
int m = (l + r) >> 1;
build(ls[o], lson);
build(rs[o], rson);
}
void update(int& o, int pre, int l, int r, ll val, int p){
o = tot++;
ls[o] = ls[pre];
rs[o] = rs[pre];
sum[o] = sum[pre] + val;
if(l == r) return;
int m = (l + r) >> 1;
if(p <= m) update(ls[o], ls[pre], lson, val, p);
else update(rs[o], rs[pre], rson, val, p);
}
ll query(int o, int ql, int qr, int l, int r){
if(ql <= l && r <= qr){
return sum[o];
}
ll ans = 0;
int m = (l + r) >> 1;
if(ql <= m) ans += query(ls[o], ql, qr, lson);
if(m < qr) ans += query(rs[o], ql, qr, rson);
return ans;
}
int main(){
int t;
scanf("%d", &t);
while(t--){
init();
int n, q;
scanf("%d", &n);
for(int i = 1; i <= n; i++){
scanf("%lld", &a[i]);
mp[i] = a[i];
}
sort(mp + 1, mp + n + 1);
int sz = unique(mp + 1, mp + n + 1) - (mp + 1);
build(root[0], 1, n);
for(int i = 1; i <= n; i++){
int pp = lower_bound(mp + 1, mp + 1 + sz, a[i]) - mp;
if(pre_pos[pp]){
int tmp; //开一棵临时的树过渡,其也是历史版本
update(tmp, root[i - 1], 1, n, -a[i], pre_pos[pp]);
update(root[i], tmp, 1, n, a[i], i);
}else{
update(root[i], root[i - 1], 1, n, a[i], i);
}
pre_pos[pp] = i;
}
scanf("%d", &q);
while(q--){
int l, r;
scanf("%d%d", &l, &r);
printf("%lld\n", query(root[r], l, r, 1, n));
}
}
return 0;
}