又名可持久化线段树
不清楚主席树是不是一定是基于值域建的,这道模板题是基于值域的可持久化线段树
找第k小类似于二分思想,左半边如果数字个数<=k,就往左边走,反之往右边走,最后停留的位置就是第k小数的位置。
计算tr的空间时用log2算完向上取整后尽量再多加1,跟线段树开4倍空间的道理差不多,不然数据强的话可能会wa
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'
#define fixed(s) fixed<<setprecision(12)<<s
//#define int long long
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 100010, M = 17;
int n, m;
int a[N];
vector<int> alls;
struct Node
{
int l, r;//左孩子 右孩子
int cnt;//这个数值范围内点的个数
}tr[N * 4 + N * M];
int root[N], idx;
int find(int x)
{
return lower_bound(alls.begin(), alls.end(), x) - alls.begin();
}
int build(int l, int r)//范围
{
int p = ++ idx;
if(l == r)return p;
int mid = l + r >> 1;
tr[p].l = build(l, mid), tr[p].r = build(mid + 1, r);
return p;
}
int insert(int p, int l, int r, int x)//p是上一个根节点
{
int q = ++ idx;
tr[q] = tr[p];
if(l == r)
{
tr[q].cnt ++;
return q;
}
int mid = l + r >> 1;
if(x <= mid)tr[q].l = insert(tr[p].l, l, mid, x);
else tr[q].r = insert(tr[p].r, mid + 1, r, x);
tr[q].cnt = tr[tr[q].l].cnt + tr[tr[q].r].cnt;
return q;
}
int query(int p, int q, int l, int r, int k)
{
if(l == r)return l;
int cnt = tr[tr[q].l].cnt - tr[tr[p].l].cnt;
int mid = l + r >> 1;
if(k <= cnt)return query(tr[p].l, tr[q].l, l, mid, k);
else return query(tr[p].r, tr[q].r, mid + 1, r, k - cnt);//记得把左半的减掉
}
int main()
{
IOS
cin >> n >> m;
for(int i = 1; i <= n; i ++)
{
cin >> a[i];
alls.push_back(a[i]);
}
sort(alls.begin(), alls.end());
alls.erase(unique(alls.begin(), alls.end()), alls.end());
root[0] = build(0, alls.size() - 1);
for(int i = 1; i <= n; i ++)
{
root[i] = insert(root[i - 1], 0, alls.size() - 1, find(a[i]));
}
while(m --)
{
int l, r, k;
cin >> l >> r >> k;
cout << alls[query(root[l - 1], root[r], 0, alls.size() - 1, k)] << endl;
}
return 0;
}
主席树也是可以进行单点修改的,以下图为例,1号点是root[1],3号点是root[2],3号点继承了一号点的左儿子,但通过3号点对左儿子进行修改时,并不会修改2号点,而是复制出一个6号点,修改的实际上是这个6号点,所以主席树某一个版本的单点修改并不会扰乱以前版本。(修改3号点时重新赋值为了5号点,因此2号点复制出来的是6号点)。
贴一个求区间内数字种数的板子
就是插入每个数x时令当前位置+1、上一个x的位置-1,因为统计种类数只需要一个数字就行了。
因为主席树可以记录历史版本,所以可以实现该操作。
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'
#define fixed(s) fixed<<setprecision(12)<<s
//#define int long long
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 1000010;
int n;
int root[N], idx;
int pos[N];
struct Node
{
int l, r;
int sum;
}tr[N * 40];
int insert(int p, int l, int r, int x, int c)
{
int q = ++ idx;
tr[q] = tr[p];
if(l == r)
{
tr[q].sum += c;
return q;
}
int mid = l + r >> 1;
if(x <= mid)tr[q].l = insert(tr[p].l, l, mid, x, c);
else tr[q].r = insert(tr[p].r, mid + 1, r, x, c);
tr[q].sum = tr[tr[q].l].sum + tr[tr[q].r].sum;
return q;
}
int query(int q, int l, int r, int ql, int qr)
{
if(ql <= l && r <= qr)return tr[q].sum;
int mid = l + r >> 1;
if(qr <= mid)return query(tr[q].l, l, mid, ql, qr);
else if(mid + 1 <= ql)return query(tr[q].r, mid + 1, r, ql, qr);
else
{
return query(tr[q].l, l, mid, ql, qr) + query(tr[q].r, mid + 1, r, ql, qr);
}
}
int main()
{
scanf("%d", &n);
int x;
for(int i = 1; i <= n; ++ i)
{
scanf("%d", &x);
root[i] = insert(root[i - 1], 1, n, i, 1);
if(pos[x])root[i] = insert(root[i], 1, n, pos[x], -1);
pos[x] = i;//pos数组用于存储a[i]的上一个出现位置,如果a[i]很大要用map
}
int m, l, r;
scanf("%d", &m);
for(int i = 1; i <= m; ++ i)
{
scanf("%d%d", &l, &r);
printf("%d\n", query(root[r], 1, n, l, n));
}
return 0;
}
1e6数据范围内2s大概率能过,也可能被卡
顺便贴一个树状数组版本的(稳过)
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'
#define fixed(s) fixed<<setprecision(12)<<s
//#define int long long
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 1000010;
int n;
int tr[N];
int ans[N];
int pos[N], a[N];
vector<PII> right1[N];//右端点
int lowbit(int x)
{
return x & -x;
}
void add(int x, int c)
{
for(int i = x; i <= n; i += lowbit(i))
{
tr[i] += c;
}
}
int sum(int x)
{
int res = 0;
for(int i = x ; i; i -= lowbit(i))
{
res += tr[i];
}
return res;
}
int main()
{
IOS
map<PII, int> mp;
cin >> n;
for(int i = 1; i <= n; i ++)
{
cin >> a[i];
}
vector<PII> res;
int m;
cin >> m;
for(int i = 1; i <= m; i ++)
{
int l, r;
cin >> l >> r;
right1[r].push_back({l, i});
}
for(int i = 1; i <= n; i ++)
{
add(i, 1);
if(pos[a[i]])add(pos[a[i]], -1);
pos[a[i]] = i;
for(auto x : right1[i])
{
ans[x.second] = sum(i) - sum(x.first - 1);
}
}
for(int i = 1; i <= m; i ++)
{
cout << ans[i] << endl;
}
return 0;
}
P2617 Dynamic Rankings - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
带修改的可持久化线段树:
修改某个根节点后势必会对后续全部根节点有影响,将后续全部根节点修改一遍的复杂度很高。
可持久化线段树和前缀和数组很像:都是T(x)=T(x-1)+c
前缀和数组可以用树状数组来解决这个问题,同理可持久化线段树亦可以。
不同的地方在于:root[i]不再基于root[i-1]进行创建,而是基于root[i]本身进行创建。
每次修改和查询的时间复杂度由log变为了log方
空间复杂度也变为了
#include <bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'
#define fixed(s) fixed<<setprecision(12)<<s
//#define int long long
using namespace std;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef long long ll;
const int N = 100010;
struct Node
{
int l, r, cnt;
}tr[N * 17 * 17];
int root[N], idx;
int n, m;
int a[N], q2[N], q3[N], q4[N];
char q1[N];
vector<int> alls;
int find(int x)
{
int l = 0, r = alls.size() - 1;
while(l < r)
{
int mid = l + r >> 1;
if(alls[mid] >= x)r = mid;
else l = mid + 1;
}
return r;
}
int lowbit(int x)
{
return x & -x;
}
int insert(int p, int l, int r, int x, int c)
{
if(!p)p = ++ idx;
if(l == r)
{
tr[p].cnt += c;
return p;
}
int mid = l + r >> 1;
if(x <= mid)tr[p].l = insert(tr[p].l, l, mid, x, c);
else tr[p].r = insert(tr[p].r, mid + 1, r, x, c);
tr[p].cnt = tr[tr[p].l].cnt + tr[tr[p].r].cnt;
return p;
}
void change(int pos, int c)
{
for(int i = pos; i <= n; i += lowbit(i))
{
root[i] = insert(root[i], 0, alls.size() - 1, find(a[pos]), c);
}
}
int t1[110], t2[110], n1, n2;//存查第k小时的节点
int kth(int l, int r, int k)
{
if(l == r)return r;
int cnt = 0;
for(int i = 1; i <= n2; i ++)
{
int p = t2[i];
cnt += tr[tr[p].l].cnt;
}
for(int i = 1; i <= n1; i ++)
{
int p = t1[i];
cnt -= tr[tr[p].l].cnt;
}
int mid = l + r >> 1;
if(k <= cnt)
{
for(int i = 1; i <= n1; i ++)t1[i] = tr[t1[i]].l;
for(int i = 1; i <= n2; i ++)t2[i] = tr[t2[i]].l;
return kth(l, mid, k);
}
else
{
for(int i = 1; i <= n1; i ++)t1[i] = tr[t1[i]].r;
for(int i = 1; i <= n2; i ++)t2[i] = tr[t2[i]].r;
return kth(mid + 1, r, k - cnt);
}
}
int query(int p, int q, int l, int r, int k)
{
n1 = n2 = 0;
for(int i = p; i; i -= lowbit(i))t1[++ n1] = root[i];
for(int i = q; i; i -= lowbit(i))t2[++ n2] = root[i];
return kth(l, r, k);
}
int main()
{
IOS
cin >> n >> m;
for(int i = 1; i <= n ; i ++)
{
cin >> a[i];
alls.push_back(a[i]);
}
for(int i = 1; i <= m; i ++)
{
cin >> q1[i];
if(q1[i] == 'Q')cin >> q2[i] >> q3[i] >> q4[i];
else
{
cin >> q2[i] >> q3[i];
alls.push_back(q3[i]);
}
}
sort(alls.begin(), alls.end());
alls.erase(unique(alls.begin(), alls.end()), alls.end());
for(int i = 1; i <= n; i ++)change(i, 1);
for(int i = 1; i <= m; i ++)
{
if(q1[i] == 'Q')
{
int pos = query(q2[i] - 1, q3[i], 0, alls.size() - 1, q4[i]);
//cout << pos << "===" << endl;
cout << alls[pos] << endl;
}
else
{
change(q2[i], -1);
a[q2[i]] = q3[i];
change(q2[i], 1);
}
}
return 0;
}