Dynamic Rankings 带修主席树(主席树 + 主席树维护树状数组)
学带修主席树的起因是vp吉林省赛的时候队友没看数据范围,然后上网查动态求逆序对,发现是带修主席树,批判我为什么不会,并要求我去学。学完之后发现带修主席树根本过不去,被卡内存了。
这场的F其实暴力即可
个人学完这个板子之后觉得板子应该是没有需要动的情况,觉得能做的就是只有动态维护每个位置上的第k小值和维护逆序对数量。
原理什么的就不说了,自己查一查资料,我觉得对于这种我改不了板子的题没啥必要去讲原理。
讲讲修改操作怎么用吧,想要在一个位置pos消除某个数值离散花后对应的v对查询的影响,直接MHST.add(pos,v,-1);
想要在一个位置pos增加某个数值离散花后对应的v对查询的影响,直接MHST.add(pos,v, 1);
对于查询操作[l, r]需要提前把对应的位置的根存起来,然后进行query查找。
真不知道这板子能咋改,所以把板子抄来对着题目学一学怎么用就行了,感觉原理不重要。
#include <iostream>
#include <cstring>
#include <iomanip>
#include <algorithm>
#include <stack>
#include <queue>
#include <numeric>
#include <cassert>
#include <bitset>
#include <cstdio>
#include <vector>
#include <unordered_set>
#include <cmath>
#include <map>
#include <unordered_map>
#include <set>
#include <deque>
#include <tuple>
#define all(a) a.begin(), a.end()
#define cnt0(x) __builtin_ctz(x)
#define endl '\n'
#define itn int
#define ll long long
#define ull unsigned long long
#define rep(i, a, b) for(int i = a;i <= b; i ++)
#define per(i, a, b) for(int i = a;i >= b; i --)
#define cntone(x) __builtin_popcount(x)
#define db double
#define fs first
#define se second
#define AC main(void)
#define HYS std::ios::sync_with_stdio(false);std::cin.tie(0);std::cout.tie(0);
typedef std::pair<int, int > PII;
typedef std::pair<int, std::pair<int, int>> PIII;
typedef std::pair<ll, ll> Pll;
typedef std::pair<double, double> PDD;
using ld = double long;
const long double eps = 1e-9;
const int N = 6e4 + 10;
int n , m, _;
int a[N];
struct node{
int l, r, cnt;
}tr[N * 35];
struct Modify_Historical_Segment_Tree{
int idx;
//s是树状数组需要用到的根节点
int root[N], S[N], maxm, stk[N], top;
//查询时用到的两个数组
int qr[N], ql[N];
inline void init(){
idx = top = 0;
}
inline int lowbit(int x) {return x & -x;}
inline int build(int l, int r){
int q = ++ idx;
tr[q].cnt = 0;
if(l == r) return q;
int mid = l + r >> 1;
tr[q].l = build(l, mid);//存的是左儿子的编号并非边界
tr[q].r = build(mid + 1, r);
return q;
}
//需要用到的上一个版本root[i - 1](返回的值是当前版本的root) l,r是离散化后的区间范围 需要添加的离散化后的值x sum是添加的数量
inline int insert(int p, int l, int r, int x, int sum){
int q = ++ idx;
tr[q] = tr[p];//复制上一个节点的信息
if(l == r){
tr[q].cnt += sum;//新版本的信息加1
return q;
}
int mid = l + r >> 1;
if(x <= mid) tr[q].l = insert(tr[p].l, l, mid, x, sum);//在左子树则需要更新信息,否则保留原本信息就可以
else tr[q].r = insert(tr[p].r, mid + 1, r, x, sum);
tr[q].cnt = tr[tr[q].l].cnt + tr[tr[q].r].cnt;
return q;
}
//位置为p 改变离散化后值为x的数量 数量
inline void add(int p, int x, int sum){
//maxm是树状数组数据离散化后的范围
while(p <= maxm){
S[p] = insert(S[p], 1, maxm, x, sum);
p += lowbit(p);
}
}
//flag 表示是查询哪个数组 1 表示qr数组 0表示ql数组 查询的是左半边修改的数量总和
inline int Sum(int x, int flag){
int res = 0;
while(x){
if(flag) res += tr[tr[qr[x]].l].cnt;
else res += tr[tr[ql[x]].l].cnt;
x -= lowbit(x);
}
return res;
}
//我们需要查询1 - lt这个区间在树状数组中左半边修改的总数量所以一般传入l - 1
//我们需要查询1 - nw这个区间在树状数组中左半边修改的总数量
// lt nw 是需要去树状数组里面求的版本号所以不用每次query不用改
inline int query(int lt, int nw, int p, int q, int l, int r, int k){//区间k小
if(l == r) return l;
int mid = l + r >> 1;
int cnt = Sum(nw, 1) - Sum(lt, 0) + tr[tr[q].l].cnt - tr[tr[p].l].cnt;
if(cnt >= k){
//因为区间是逐渐缩小的,所有我们对应 方便Sum去求一个子区间的修改的总数量
for(int i = nw; i; i -= lowbit(i)) qr[i] = tr[qr[i]].l;
for(int i = lt; i; i -= lowbit(i)) ql[i] = tr[ql[i]].l;
return query(lt, nw, tr[p].l, tr[q].l, l, mid, k);
}
else{
for(int i = nw; i; i -= lowbit(i)) qr[i] = tr[qr[i]].r;
for(int i = lt; i; i -= lowbit(i)) ql[i] = tr[ql[i]].r;
return query(lt, nw, tr[p].r, tr[q].r, mid + 1, r, k - cnt);
}
}
}MHST;
struct Query{
int op, l, r, k;
}q[N];
inline void solve(){
std::cin >> n >> m;
std::vector<int> nums;
MHST.init();
std::vector<int> pos(n + 1);
for(int i = 1; i <= n; i ++){
std::cin >> a[i];
nums.emplace_back(a[i]);
}
for(int i = 1; i <= m; i ++){
char ch;
std::cin >> ch;
if(ch == 'Q'){
q[i].op = 1;
std::cin >> q[i].l >> q[i].r >> q[i].k;
}
else{
q[i].op = 0;
std::cin >> q[i].l >> q[i].r;
nums.emplace_back(q[i].r);
}
}
std::sort(nums.begin(), nums.end());
nums.erase(std::unique(nums.begin(), nums.end()), nums.end());
int sz = nums.size();
MHST.maxm = sz;
for(int i = 1; i <= n; i ++) pos[i] = std::lower_bound(nums.begin(), nums.end(), a[i]) - nums.begin() + 1;
auto &root = MHST.root, &S = MHST.S, &qr = MHST.qr, &ql = MHST.ql;
root[0] = MHST.build(1, sz);
for(int i = 1; i <= n; i ++) root[i] = MHST.insert(root[i - 1], 1, sz, pos[i], 1);
for(int i = 1; i <= sz; i ++) S[i] = root[0];
for(int i = 1; i <= m; i ++){
if(q[i].op){
int l = q[i].l - 1, r = q[i].r, k = q[i].k;
for(int j = r; j; j -= MHST.lowbit(j)) qr[j] = S[j];
for(int j = l; j; j -= MHST.lowbit(j)) ql[j] = S[j];
std::cout << nums[MHST.query(l, r, root[l], root[r], 1, sz, k) - 1] << '\n';
}else{
int x = std::lower_bound(nums.begin(), nums.end(), a[q[i].l]) - nums.begin() + 1;
MHST.add(q[i].l, x, -1);
a[q[i].l] = q[i].r;
x = std::lower_bound(nums.begin(), nums.end(), a[q[i].l]) - nums.begin() + 1;
MHST.add(q[i].l, x, 1);
}
}
}
signed AC{
HYS
std::cin >> _;
while(_ --)
solve();
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框架的用法!