珂朵莉树
features
- 暴力,实现简单
- 本质思想是分块,相同值连在一起的算一块,用set维护
- 适用于大区间赋值问题(若每次赋值的区间很小,则退化)
- 容易被卡(
code
cf915E 动态开点/珂朵莉树
注意要开快读快输
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
struct node{
ll l,r;
mutable int v;
node(ll l, ll r, int v):l(l),r(r),v(v){}
bool operator<(const node &o) const {return l<o.l;}
};
set<node> tree;
auto split(ll pos){
auto it = tree.lower_bound(node(pos,0,0));
if(it!=tree.end()&&it->l == pos) return it;
it--;
ll l = it->l,r=it->r;int v=it->v;
tree.erase(it);
tree.insert(node(l,pos-1,v));
return tree.insert(node(pos,r,v)).first;
}
int ans;
void assign(ll l, ll r, int v){
auto end = split(r+1),begin = split(l),it=begin;
int len = 0,workdays = 0;
for(;it!=end;++it){
len += it->r-it->l+1;
workdays += (it->r-it->l+1)*(it->v-1);
}
tree.erase(begin,end);
tree.insert(node(l,r,v));
if(v-1){
ans += len - workdays;
}else{
ans -= workdays;
}
}
inline int read(){
int j=1,k=0;
char ch = getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') j=-1;ch=getchar();
}
while(ch>='0'&&ch<='9'){
k=k*10+ch-'0';ch=getchar();
}
return k*j;
}
inline void print(int x){
if(x < 10) putchar('0' + x); else print(x / 10), print(x % 10);
}
int main(){
int n,q;n=read(),q=read();
tree.insert(node(1,n,2));
ans = n;
while(q--){
int l = read(),r = read(),k = read();
assign(l,r,k);
print(ans);
putchar(10);
}
}
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
inline int read() {int f=1,x=0;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}return f*x;}
inline void print(ll x) {if(x<10)putchar('0'+x);else print(x/10),print(x%10);}
ll seed,n,m,vmax;
int op;
ll x,y,l,r;
ll a[100004];
struct node{
ll l,r;
mutable ll v;
node(ll l,ll r,ll v):l(l),r(r),v(v){}
bool operator<(const node &o) const{return l<o.l;}
};
set<node> tree;
auto split(ll pos){
auto it = tree.lower_bound(node(pos,0,0));
if(it!=tree.end()&&it->l==pos) return it;
it--;
ll l=it->l,r=it->r,v=it->v;
tree.erase(it);
tree.insert(node(l,pos-1,v));
return tree.insert(node(pos,r,v)).first;
}
ll qpow(ll a,ll n,ll y){
ll ans = 1;
a%=y;
while(n){
if(n&1) ans=ans*a%y;
a=a*a%y;
n>>=1;
}
return ans;
}
ll assign(ll l,ll r,int op,ll v,ll y){
auto end = split(r+1),begin = split(l),it=begin;
if(op==1){
// add
for(;it!=end;++it){
it->v+=v;
}
}else if(op==2){
tree.erase(begin,end);
tree.insert(node(l,r,v));
}else if(op==3){
vector<pair<ll,ll>> nod; // 节点值和区间长度
for(;it!=end;++it) nod.push_back(make_pair(it->v,it->r-it->l+1));
sort(nod.begin(),nod.end());
for(int i=0;i<nod.size();++i){
v -= nod[i].second;
if(v<=0) return nod[i].first;
}
}else{
ll ans = 0;
for(;it!=end;++it){
ans = (ans+(it->r-it->l+1)*qpow(it->v,x,y)%y)%y;
}
return ans;
}
return 0;
}
int rnd(){
ll ret = seed;
seed = (seed*7+13)%1000000007;
return ret;
}
void generate(){
for(int i=1;i<=n;++i){
a[i] = (rnd()%vmax)+1;
tree.insert(node(i,i,a[i]));
}
for(int i=1;i<=m;++i){
op = (rnd()%4)+1;
l = (rnd()%n)+1;
r = (rnd()%n)+1;
if(l>r) swap(l,r);
if(op==3) x=(rnd()%(r-l+1))+1;
else x=(rnd()%vmax)+1;
if(op==4) y=(rnd()%vmax)+1;
if(op==1){
assign(l,r,1,x,0);
}else if(op==2){
assign(l,r,2,x,0);
}else if(op==3){
print(assign(l,r,3,x,0));
putchar(10);
}else print(assign(l,r,4,x,y)),putchar(10);
}
}
int main(){
n=read(),m=read(),seed=read(),vmax=read();
generate();
}
线段树进阶
动态开点
- 空间复杂度: O ( m l o g m ) O(mlogm) O(mlogm)
#include <bits/stdc++.h>
using namespace std;
#define ls(x) tree[x].ls
#define rs(x) tree[x].rs
#define val(x) tree[x].val
#define lzy(x) tree[x].lzy
const int maxm = 15000004;
int cnt;
struct{
int ls,rs,val,lzy;
}tree[maxm]; // 能开多大开多大
void push_up(int rt){
val(rt) = val(ls(rt))+val(rs(rt));
}
void push_down(int rt,int m){
if(!ls(rt)) ls(rt) = ++cnt;
if(!rs(rt)) rs(rt) = ++cnt;
if(lzy(rt)){
lzy(ls(rt)) = lzy(rt);
lzy(rs(rt)) = lzy(rt);
val(ls(rt)) = (lzy(rt)-1)*(m-(m>>1));
val(rs(rt)) = (lzy(rt)-1)*(m>>1);
lzy(rt) = 0;
}
}
void update(int a,int b,int c,int l,int r,int rt){
// cout<<a<<" "<<b<<" "<<l<<" "<<r<<" "<<rt<<" "<<val(rt)<<" "<<lzy(rt)<<endl;
if(a<=l&&b>=r){
val(rt) = c*(r-l+1);
lzy(rt) = c+1;
return;
}
push_down(rt,r-l+1);
int mid = (l+r)>>1;
if(a<=mid) update(a,b,c,l,mid,ls(rt));
if(b>mid) update(a,b,c,mid+1,r,rs(rt));
push_up(rt);
}
int main(){
int n,q;scanf("%d%d",&n,&q);
val(1)=n; // 初始化第一个节点
lzy(1)=2;
cnt++;
for(int i=1;i<=q;++i){
int l,r,k;scanf("%d%d%d",&l,&r,&k);
k--;
update(l,r,k,1,n,1);
printf("%d\n",val(1));
}
}
权值线段树
普通线段树:维护一个区间中的数的属性。
权值线段树:维护一个区间中,在一个值域中的数的数目。
就是用线段树来维护一个桶。没什么特殊的。很容易实现。
可持久化线段树
可持久化数组(可持久化线段树)
- 区间修改:标记永久化
#include<bits/stdc++.h>
using namespace std;
int cnt=1;
const int maxn = 5e6+5;
int read(){int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}return x*f;}
#define ls(x) tree[x].ls
#define rs(x) tree[x].rs
#define val(x) tree[x].val
struct{
int val,ls,rs;
}tree[maxn<<2];
// 单点修改,单点查询:可持久化数组
int arr[maxn],root[maxn];
void build(int l,int r,int rt){
if(l==r) val(rt) = arr[l];
else {
ls(rt) = ++cnt,rs(rt)=++cnt;
int mid = (l+r)>>1;
build(l,mid,ls(rt));
build(mid+1,r,rs(rt));
}
}
void update(int a,int l,int r,int v,int rt,int nrt){
if(l==r) val(nrt) = v;
else{
ls(nrt) = ls(rt),rs(nrt)=rs(rt);
int mid = (l+r)>>1;
if(a<=mid) ls(nrt)=++cnt,update(a,l,mid,v,ls(rt),ls(nrt));
else rs(nrt) = ++cnt,update(a,mid+1,r,v,rs(rt),rs(nrt));
}
}
int query(int l,int r,int a,int rt){
if(l==r) return val(rt);
else {
int mid = (l+r)>>1;
if(a<=mid) return query(l,mid,a,ls(rt));
else return query(mid+1,r,a,rs(rt));
}
}
int main(){
int n,m;scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i){
scanf("%d",&arr[i]);
}
build(1,n,1);
root[0]=1;
for(int i=1;i<=m;++i){
int v,id,loc,val;
scanf("%d%d",&v,&id);
if(id==1){
scanf("%d%d",&loc,&val);
root[i] = ++cnt;
update(loc,1,n,val,root[v],root[i]);
}else{
scanf("%d",&loc);
root[i] = root[v];
printf("%d\n",query(1,n,loc,root[v]));
}
}
}
#include<bits/stdc++.h>
using namespace std;
const int maxn = 5e6+5;
int cnt = 1;
int arr[maxn],root[maxn];
typedef long long ll;
#define ls(x) tree[x].ls
#define rs(x) tree[x].rs
#define val(x) tree[x].val
#define lzy(x) tree[x].lzy
struct{
ll val;
int ls,rs;
ll lzy;
}tree[maxn<<2];
void build(int l,int r,int rt){
if(l==r){
val(rt) = arr[l];
return;
}
ls(rt) = ++cnt,rs(rt) = ++cnt;
int mid = (l+r)>>1;
build(l,mid,ls(rt));
build(mid+1,r,rs(rt));
val(rt) = val(ls(rt))+val(rs(rt));
}
void update(int a,int b,ll c,int l,int r,int rt,int nrt){
ls(nrt) = ls(rt), rs(nrt) = rs(rt), lzy(nrt) = lzy(rt);
if (b>=r&&a<=l) {
lzy(nrt) += c;
}else{
int mid = (l+r)>>1;
if(a<=mid) ls(nrt) = ++cnt, update(a,b,c,l,mid,ls(rt),ls(nrt));
if(b>mid) rs(nrt) = ++cnt, update(a,b,c,mid+1,r,rs(rt),rs(nrt));
}
val(nrt) = val(rt) + (min(b,r) - max(a,l)+1)*c;
}
ll query(int a,int b,int l,int r,int rt,ll mk){ // 标记永久化
if(b>=r&&a<=l) return val(rt) + mk*(r-l+1);
int mid = (l+r)>>1;
ll res = 0;
if(a<=mid) res += query(a,b,l,mid,ls(rt),mk+lzy(rt));
if(b>mid) res += query(a,b,mid+1,r,rs(rt),mk+lzy(rt));
return res;
}
int main(){
int n,m;
int isf = 1;
while(~scanf("%d%d",&n,&m)){
if(!isf) cout<<endl;
isf=0;
for(int i=1;i<=n;++i)
scanf("%d",&arr[i]);
root[0]=1;
build(1,n,1);
int time = 0; // the current time stamp
for(int i=1;i<=m;++i){
string s;cin>>s;
if(s=="C"){
int l,r,d;scanf("%d%d%d",&l,&r,&d);
root[++time] = ++cnt;
update(l,r,d,1,n,root[time-1],root[time]);
}else if(s=="Q"){
int l,r;scanf("%d%d",&l,&r);
cout<<query(l,r,1,n,root[time],0)<<endl;
}else if(s=="H"){
int l,r,t;scanf("%d%d%d",&l,&r,&t);
cout<<query(l,r,1,n,root[t],0)<<endl;
}else{
int t;scanf("%d",&t);
time = t;
}
}
}
}
主席树
洛谷3834 可持久化线段树【主席树】
区间第K大。
点查询、点修改。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e6+5;
int A[maxn],root[maxn],cnt=1;
int c[maxn],L[maxn],ori[maxn];
#define num(x) tree[x].num
#define ls(x) tree[x].ls
#define rs(x) tree[x].rs
struct{
int num;
int ls,rs;
}tree[maxn<<2];
void build(){
root[0] = 1;
num(1) = 0;
ls(1)=1;
rs(1)=1;
}
void update(int a,int l,int r,int rt,int nrt){
num(nrt) = num(rt);
if(l==r) num(nrt)++;
else{
ls(nrt) = ls(rt),rs(nrt) = rs(rt);
int mid = (l+r)>>1;
if(a<=mid) ls(nrt)=++cnt,update(a,l,mid,ls(rt),ls(nrt));
else rs(nrt)=++cnt,update(a,mid+1,r,rs(rt),rs(nrt));
num(nrt) = num(ls(nrt)) + num(rs(nrt));
}
}
// 查询区间第a大
int query(int a,int l,int r,int p,int q){
// cout<<a<<" "<<l<<" "<<r<<" "<<p<<" "<<q<<endl;
if(l==r) return l;
else{
int mid = (l+r)>>1;
if(a<=num(ls(q))-num(ls(p))) return query(a,l,mid,ls(p),ls(q));
else return query(a-(num(ls(q))-num(ls(p))),mid+1,r,rs(p),rs(q));
}
}
int discretize(int n){
for(int i=0;i<n;++i) c[i]=A[i];
sort(c,c+n);
int len = unique(c,c+n)-c;
int maxx = 0;
for(int i=0;i<n;++i){
L[i] = lower_bound(c,c+len,A[i])-c+1; // 查找
maxx = max(maxx,L[i]);
ori[L[i]] = A[i]; // 离散化后的数对应的原数
}
return maxx;
}
int main(){
int n,m;scanf("%d%d",&n,&m);
for(int i=0;i<n;++i) scanf("%d",&A[i]);
build();
int maxx = discretize(n);
for(int i=0;i<n;++i){
root[i+1] = ++cnt;
update(L[i],1,maxx,root[i],root[i+1]);
}
for(int i=1;i<=m;++i){
int l,r,k;scanf("%d%d%d",&l,&r,&k);
int id = query(k,1,maxx,root[l-1],root[r]);
cout<<ori[id]<<endl;
}
}
可持久化平衡树
无旋Treap
码量小,常数小。
除了LCT问题亚于splay,其他方面都可以替代其他平衡树。
核心操作:split和merge。其他操作都是基于这两个操作。
loj104 普通平衡树
#include<bits/stdc++.h>
using namespace std;
const int maxn = 4e5+6;
struct node{
int key,lch,rch,priority,sz;
node(int k=0,int l=0,int r=0,int p=0,int s=0):key(k),lch(l),rch(r),priority(p),sz(s){}
}tree[maxn<<2];
int cnt;
int root; // 根
// 小根堆
// 可重
#define key(x) tree[x].key
#define pri(x) tree[x].priority
#define lch(x) tree[x].lch
#define rch(x) tree[x].rch
#define sz(x) tree[x].sz
int newnode(int v){
tree[++cnt] = node(v,0,0,rand(),1);
return cnt;
}
void push_up(int u){
sz(u) = sz(lch(u)) + sz(rch(u))+1;
}
// 分裂成两个treap,第一个treap所有节点的关键值小于等于key
// 第二个treap所有节点的关键值大于key
pair<int,int> split(int u,int key){
if(!u) return make_pair(0,0);
if(key<key(u)) {
pair<int,int> o = split(lch(u),key);
lch(u) = o.second;
push_up(u);
return make_pair(o.first,u);
}else{
pair<int,int> o = split(rch(u),key);
rch(u) = o.first;
push_up(u);
return make_pair(u,o.second);
}
}
// 现有两棵treap,满足u中所有的key小于等于v中所有的key。我们现在将他们两个合并。
int merge(int u,int v){
if(!u||!v) return u+v;
if(pri(u)<pri(v)){
rch(u) = merge(rch(u),v);
push_up(u);
return u;
}else{
lch(v) = merge(u,lch(v));
push_up(v);
return v;
}
}
// 查询排名为k的点
int find_rank(int u,int k){
if(!u) return 0;
if(k<=sz(lch(u))) return find_rank(lch(u),k);
else if(k==sz(lch(u))+1) return u;
else return find_rank(rch(u),k-sz(lch(u))-1);
}
// 查询值为key的排名,若有多个,输出最小排名
int get_rank(int u,int key){
pair<int,int> o = split(u,key-1);
int num = sz(o.first)+1;
root = merge(o.first,o.second);
return num;
}
void insert(int key){
pair<int,int> o = split(root,key);
o.first = merge(o.first,newnode(key));
root = merge(o.first,o.second);
}
// 删除一个键值为key的点
void erase(int key){
pair<int,int> o = split(root,key); // 左:小于等于Key,右:大于key
pair<int,int> p = split(o.first,key-1); // 左:小于等于key-1,右:等于key
p.second = merge(lch(p.second),rch(p.second)); // 删掉根节点
root = merge(p.first,merge(p.second,o.second));
}
int get_precursor(int key){
pair<int,int> o = split(root,key-1);
int num = key(find_rank(o.first,sz(o.first)));
merge(o.first,o.second);
return num;
}
int get_successor(int key){
pair<int,int> o = split(root,key);
int num = key(find_rank(o.second,1));
root = merge(o.first,o.second);
return num;
}
int main(){
int n;scanf("%d",&n);
srand(time(NULL));
for(int i=1;i<=n;++i){
int opt,x;scanf("%d%d",&opt,&x);
switch (opt) {
case 1:insert(x);break;
case 2:erase(x);break;
case 3:cout<<get_rank(root,x)<<endl;break;
case 4:cout<<key(find_rank(root,x))<<endl;break;
case 5:cout<<get_precursor(x)<<endl;break;
case 6:cout<<get_successor(x)<<endl;break;
}
}
}
可持久化
在两个核心操作上加点即可。
pair<int,int> split(int u,int key){
if(!u) return make_pair(0,0);
int nrt = ++cnt;
tree[nrt] = tree[u];
if(key<key(u)) {
pair<int,int> o = split(lch(u),key);
lch(nrt) = o.second;
push_up(nrt);
return make_pair(o.first,nrt);
}else{
pair<int,int> o = split(rch(u),key);
rch(nrt) = o.first;
push_up(nrt);
return make_pair(nrt,o.second);
}
}
int merge(int u,int v){
if(!u||!v) return u+v;
int nrt = ++cnt;
if(pri(u)<pri(v)){
tree[nrt] = tree[u];
rch(nrt) = merge(rch(u),v);
}else{
tree[nrt] = tree[v];
lch(nrt) = merge(u,lch(v));
}
push_up(nrt);
return nrt;
}
然后你会发现如果权值相同的点很多,treap会退化。。。。
此时有一种神奇的方法,通过概率进行合并。
来源:Bili Young’s blog
模板题:
洛谷P3835 可持久化平衡树
可持久化并查集
洛谷模板
思路很简单,就是在普通并查集上面叠加了大于log的时间复杂度。
就是把fa数组构建成一棵树(可持久化线段树(可持久化数组))。点修改点查询。为什么不用数组(查询
O
(
1
)
O(1)
O(1)修改
O
(
1
)
O(1)
O(1)),废话,当然是因为要可持久化啦。
合并方式也从路径压缩改成按秩合并。