数据结构

线段树

模板

线段树一般处理区间操作和可合并类信息
线段树一

#include<bits/stdc++.h>
#define install int mid=(l+r)>>1
using namespace std;
typedef long long ll;
const int N=100009;
ll sum[N<<2],tag[N<<2],basic[N];
void pu(int i){sum[i]=sum[i<<1]+sum[i<<1|1];}
void pd(int i,int l,int r){
    install; 
    tag[i<<1]+=tag[i],tag[i<<1|1]+=tag[i],sum[i<<1]+=(mid-l+1)*tag[i],sum[i<<1|1]+=(r-mid)*tag[i],tag[i]=0;
}
void build(int i,int l,int r){if(l==r){sum[i]=basic[l]; return;} install; build(i<<1,l,mid),build(i<<1|1,mid+1,r),pu(i);}
void modify(int i,int l,int r,int L,int R,ll k){
    if(l==L&&r==R){tag[i]+=k,sum[i]+=(r-l+1)*k; return;}
    install; pd(i,l,r);
    if(R<=mid) modify(i<<1,l,mid,L,R,k); 
    else if(L>mid) modify(i<<1|1,mid+1,r,L,R,k); 
    else modify(i<<1,l,mid,L,mid,k),modify(i<<1|1,mid+1,r,mid+1,R,k); pu(i);
}
ll query(int i,int l,int r,int L,int R){
    if(l==L&&r==R) return sum[i];
    install; pd(i,l,r); 
    if(R<=mid) return query(i<<1,l,mid,L,R); 
    else if(L>mid) return query(i<<1|1,mid+1,r,L,R); 
    else return query(i<<1,l,mid,L,mid)+query(i<<1|1,mid+1,r,mid+1,R);
}
int main(){
    int n,m,op,x,y; ll k; scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%lld",&basic[i]); build(1,1,n);
    while(m--){
        scanf("%d",&op); 
        if(op==1) scanf("%d%d%lld",&x,&y,&k),modify(1,1,n,x,y,k); 
        else scanf("%d%d",&x,&y),printf("%lld\n",query(1,1,n,x,y));
    }
    return 0;
}

线段树二

#include<bits/stdc++.h>
#define install int mid=(l+r)>>1
using namespace std;
typedef long long ll;
const int N=100009;
ll sum[N<<2],tag[N<<2],mul[N<<2],basic[N],mod;
void solve(int i){sum[i]%=mod,tag[i]%=mod,mul[i]%=mod;}
void pu(int i){sum[i]=sum[i<<1]+sum[i<<1|1];}
void pd(int i,int l,int r){
    install; solve(i);
    mul[i<<1]*=mul[i],mul[i<<1|1]*=mul[i];
    tag[i<<1]*=mul[i],tag[i<<1|1]*=mul[i];
    tag[i<<1]+=tag[i],tag[i<<1|1]+=tag[i];
    sum[i<<1]*=mul[i],sum[i<<1|1]*=mul[i];
    sum[i<<1]+=(mid-l+1)*tag[i],sum[i<<1|1]+=(r-mid)*tag[i];
    mul[i]=1,tag[i]=0;
    solve(i<<1),solve(i<<1|1);
}
void build(int i,int l,int r){mul[i]=1; if(l==r){sum[i]=basic[l]%mod; return;} install; build(i<<1,l,mid),build(i<<1|1,mid+1,r),pu(i);}
void modify(int i,int l,int r,int L,int R,ll k){
    if(l==L&&r==R){tag[i]+=k,sum[i]+=(r-l+1)*k,solve(i); return;}
    install; pd(i,l,r);
    if(R<=mid) modify(i<<1,l,mid,L,R,k); 
    else if(L>mid) modify(i<<1|1,mid+1,r,L,R,k); 
    else modify(i<<1,l,mid,L,mid,k),modify(i<<1|1,mid+1,r,mid+1,R,k); pu(i);
}
void modify2(int i,int l,int r,int L,int R,ll k){
    if(l==L&&r==R){tag[i]*=k,sum[i]*=k,mul[i]*=k,solve(i); return;}
    install; pd(i,l,r);
    if(R<=mid) modify2(i<<1,l,mid,L,R,k); 
    else if(L>mid) modify2(i<<1|1,mid+1,r,L,R,k); 
    else modify2(i<<1,l,mid,L,mid,k),modify2(i<<1|1,mid+1,r,mid+1,R,k); pu(i);
}
ll query(int i,int l,int r,int L,int R){
    if(l==L&&r==R) return sum[i];
    install; pd(i,l,r); 
    if(R<=mid) return query(i<<1,l,mid,L,R); 
    else if(L>mid) return query(i<<1|1,mid+1,r,L,R); 
    else return (query(i<<1,l,mid,L,mid)+query(i<<1|1,mid+1,r,mid+1,R))%mod;
}
int main(){
    int n,m,op,x,y; ll k; scanf("%d%d%lld",&n,&m,&mod); for(int i=1;i<=n;i++) scanf("%lld",&basic[i]); build(1,1,n);
    while(m--){
        scanf("%d",&op); 
        if(op==1) scanf("%d%d%lld",&x,&y,&k),modify2(1,1,n,x,y,k); 
        else if(op==2) scanf("%d%d%lld",&x,&y,&k),modify(1,1,n,x,y,k);
        else scanf("%d%d",&x,&y),printf("%lld\n",(query(1,1,n,x,y)+mod)%mod);
    }
    return 0;
}

这个算法是很多算法的基础
前置知识:线段树基础

动态开点线段树

这个东西的思想是:结点只有在有需要的时候才被创建。
我们进行递归时如果某个节点不存在就新建一个
操作有点像 \(\text{Treap}\) 的新建操作
这样每次操作最多新建 \(\text{log}\) 个节点
空间复杂度 \(O(n\log n)\to O(m\log m)\)
其中 \(n\) 为值域,\(m\) 为操作数
例题 P2781
顺便说一句如果你死调 40 可能是宏定义的奇妙 bug
pushdown 的时候要没有对应节点就自己建一个

#include<bits/stdc++.h>
#define size(l,r) (ll)((r)-(l)+1)
#define Debug(i,l,r) printf("%d(%d-%d) sum=%lld tag=%lld\n",i,l,r,k[i],tag[i])
using namespace std;
typedef long long ll;
const int N=300009;
ll k[N],tag[N];
int cnt,ls[N],rs[N];
class T{
public:
	int root=0;
	void modify(int &i,int l,int r,int L,int R,ll d){
		if(!i)i=++cnt;
		if(L==l&&R==r){tag[i]+=d,k[i]+=size(l,r)*d;return;}
		pushdown(i,l,r);
		int mid=(l+r)>>1;
		if(R<=mid)modify(ls[i],l,mid,L,R,d);
		else if(L>mid)modify(rs[i],mid+1,r,L,R,d);
		else modify(ls[i],l,mid,L,mid,d),modify(rs[i],mid+1,r,mid+1,R,d);
		k[i]=k[ls[i]]+k[rs[i]];
	}
	ll query(int i,int l,int r,int L,int R){
		if(!i)return 0;
		if(L==l&&R==r)return k[i];
		pushdown(i,l,r);
		int mid=(l+r)>>1;
		if(R<=mid)return query(ls[i],l,mid,L,R);
		else if(L>mid)return query(rs[i],mid+1,r,L,R);
		else return query(ls[i],l,mid,L,mid)+query(rs[i],mid+1,r,mid+1,R);
	}
private:
	void pushdown(int i,int l,int r){
		if(l==r){tag[i]=0;return;}
		if(!tag[i])return;
		if(!ls[i])ls[i]=++cnt;
		if(!rs[i])rs[i]=++cnt;
		tag[ls[i]]+=tag[i],tag[rs[i]]+=tag[i];
		int mid=(l+r)>>1;
		k[ls[i]]+=size(l,mid)*tag[i],k[rs[i]]+=size(mid+1,r)*tag[i],tag[i]=0;
	}
}t;
int main(){
	int n,m,l,r,op;
	ll d;
	scanf("%d%d",&n,&m);
	while(m--){
		scanf("%d%d%d",&op,&l,&r);
		if(op==1)scanf("%lld",&d),t.modify(t.root,1,n,l,r,d);
		else printf("%lld\n",t.query(t.root,1,n,l,r));
	}
	return 0;
}
权值线段树

权值线段树是以值域为维护对象的线段树
比如说普通平衡树这道题
我们记录的其实是每个点的数字数量
插入就将对应的点加一,删除同理减一
我们发现其中的前驱后继都可以用排名和反排名来解决
排名我们有天生求和优势
反排名二分搞一下就行了
至于值域,动态开点!
这样就可以支持在线了
但他的空间复杂度和正经平衡树相比还是略逊一些
像加强版会被卡空间
码量堪比 \(\text{01Trie}\) 的赛博代码

#include<bits/stdc++.h>
using namespace std;
const int N=2200005,n=10000009;
int k[N],ls[N],rs[N],cnt;
struct T{
	int root=0;
	void modify(int &i,int l,int r,int d,int op){
		if(!i)i=++cnt;
		if(l==r){k[i]+=op;return;}
		int mid=(l+r)>>1;
		if(d<=mid) modify(ls[i],l,mid,d,op); else modify(rs[i],mid+1,r,d,op);
		k[i]=k[ls[i]]+k[rs[i]];
	}
	int rank(int i,int l,int r,int L,int R){
		if(!i)return 0;
		if(l==L&&r==R)return k[i];
		int mid=(l+r)>>1;
		if(R<=mid) return rank(ls[i],l,mid,L,R); else if(L>mid) return rank(rs[i],mid+1,r,L,R);
		return rank(ls[i],l,mid,L,mid)+rank(rs[i],mid+1,r,mid+1,R);
	}
	int kth(int d){
		int i=root,l=-n,r=n,mid;
		while(l<r){
			mid=(l+r)>>1;
			if(k[ls[i]]>=d) i=ls[i],r=mid; else d-=k[ls[i]],i=rs[i],l=mid+1;
		}
		return l;
	}
}t;
int main(){
	int m,op,x;
	scanf("%d",&m);
	while(m--){
		scanf("%d%d",&op,&x);
		switch(op){
		case 1:t.modify(t.root,-n,n,x,1);break;
		case 2:t.modify(t.root,-n,n,x,-1);break;
		case 3:printf("%d\n",t.rank(t.root,-n,n,-n,x-1)+1);break;
		case 4:printf("%d\n",t.kth(x));break;
		case 5:printf("%d\n",t.kth(t.rank(t.root,-n,n,-n,x-1)));break;
		case 6:printf("%d\n",t.kth(t.rank(t.root,-n,n,-n,x)+1));break;
		}
	}
	return 0;
}
可持久化线段树
P3834 静态区间第 k 小

这题用到上面两点
把数列拆分成 \([1,1][1,2]\dots[1,n]\)
对每个子区间建一棵权值线段树
\(k\) 小就是我们前面的排名
所以采用可持久化方法
既然每次都是单修
那我们可以考虑在原来的基础上新建一条链
这个显然是动态开点的
但我们初始的树不能动态开点,因为这样我们会在建链的时候发现缺少对应节点
那这样我们就只能离散化了
处理查询的时候我们把两个对应的排名相减
二分去搞即可
空间应当是 \(O(4n+n\log n)=44e5\)
注意修改操作到了递归边界时需特殊处理!

#include<bits/stdc++.h>
using namespace std;
const int N=4400000,M=200001;
int len,tmp[M],cnt,k[N],ls[N],rs[N],root[M];
vector<int> busket;
struct T{
	void build(int &i=root[0],int l=1,int r=len){
		i=++cnt;
		if(l==r)return;
		int mid=(l+r)>>1;
		build(ls[i],l,mid),build(rs[i],mid+1,r);
	}
	void modify(int &i,int I,int l,int r,int d){
		i=++cnt;
		if(l==r){k[i]=k[I]+1;return;}
		int mid=(l+r)>>1;
		if(d<=mid) modify(ls[i],ls[I],l,mid,d),rs[i]=rs[I]; else modify(rs[i],rs[I],mid+1,r,d),ls[i]=ls[I];
		k[i]=k[ls[i]]+k[rs[i]];
	}
	int query(int x,int y,int l,int r,int d){
		if(l==r)return l;
		int mid=(l+r)>>1;
		if(d<=k[ls[y]]-k[ls[x]]) return query(ls[x],ls[y],l,mid,d); else return query(rs[x],rs[y],mid+1,r,d-k[ls[y]]+k[ls[x]]);
	}
}t;
int main(){
	int tlen,n,T,l,r,d;
	scanf("%d%d",&n,&T);
	for(int i=1;i<=n;i++)scanf("%d",tmp+i),busket.push_back(tmp[i]);
	sort(busket.begin(),busket.end());
	tlen=unique(busket.begin(),busket.end())-busket.begin(),len=tlen+1,t.build();
	for(int i=1;i<=n;i++)tmp[i]=lower_bound(busket.begin(),busket.begin()+tlen,tmp[i])-busket.begin()+1,t.modify(root[i],root[i-1],1,len,tmp[i]);
	while(T--)scanf("%d%d%d",&l,&r,&d),printf("%d\n",busket[t.query(root[l-1],root[r],1,len,d)-1]);
}
带修版本P2617

全名:树状数组套动态开点权值线段树
普通的主席树之所以不能带修是因为修改一个数会至多影响 \(n\) 棵树
极限复杂度会被卡到 \(O(n^2\log n)\)
而我们干脆抛弃主席树的结构
直接 \(n\) 颗动态开点
但是我们此时每棵树的表示范围变为 \([x-lowbit+1,x]\)
然后查询和修改就直接和树状数组类似
这样每个节点的修改就只会影响 \(log\) 颗线段树
本题需要离散化和离线否则卡不过空间
时空复杂度 \(O(n\log^2n)\)
推荐大数组直接 \(3\times10^7\)

#include<bits/stdc++.h>
using namespace std;
const int N=30000000,M=100002,Q=200002;
int cnt,ls[N],rs[N],k[N],tmp[M],length;
vector<int> busket,ADD,MINUS;
struct T{
	int root;
	void modify(int &i,int l,int r,int d,int op){
		if(!i)i=++cnt;
		if(l==r){k[i]+=op;return;}
		int mid=(l+r)>>1;
		if(d<=mid)modify(ls[i],l,mid,d,op); else modify(rs[i],mid+1,r,d,op);
		k[i]=k[ls[i]]+k[rs[i]];
	}
}t[M];
struct Question{
	bool ismodify;
	int l,r,d;
}q[M];
int a[M];
int main(){
	int n,m,l,r,tot,l1,l2;
	char op[3];
	scanf("%d%d",&n,&m),busket.push_back(INT_MIN);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]),busket.push_back(a[i]);
	for(int i=1;i<=m;i++){
		scanf("%s",op),q[i].ismodify=(op[0]=='C');
		if(q[i].ismodify) scanf("%d%d",&q[i].l,&q[i].d),busket.push_back(q[i].d);
		else scanf("%d%d%d",&q[i].l,&q[i].r,&q[i].d);
	}
	sort(busket.begin(),busket.end()),length=unique(busket.begin(),busket.end())-busket.begin();
	for(int i=1;i<=n;i++) a[i]=lower_bound(busket.begin(),busket.begin()+length,a[i])-busket.begin();
	for(int i=1;i<=m;i++) if(q[i].ismodify) q[i].d=lower_bound(busket.begin(),busket.begin()+length,q[i].d)-busket.begin();
	--length;
	for(int i=1;i<=n;i++) for(int j=i;j<=n;j+=(j&-j)) t[j].modify(t[j].root,1,length,a[i],1);
	for(int it=1;it<=m;it++){
		if(q[it].ismodify){
			for(int i=q[it].l;i<=n;i+=(i&-i)) t[i].modify(t[i].root,1,length,a[q[it].l],-1),t[i].modify(t[i].root,1,length,q[it].d,1);
			a[q[it].l]=q[it].d;
		}else{
			ADD.clear(),MINUS.clear(),l=1,r=length,l1=0,l2=0;
			for(int i=q[it].r;i>0;i-=(i&-i))ADD.push_back(t[i].root),l1++;
			for(int i=q[it].l-1;i>0;i-=(i&-i))MINUS.push_back(t[i].root),l2++;
			while(l<r){
				tot=0;
				for(int i:ADD)tot+=k[ls[i]];
				for(int i:MINUS)tot-=k[ls[i]];
				// printf("test %d[%d-%d] rank %d need %d\n",busket[(l+r)>>1],busket[l],busket[r],tot,q[it].d);
				if(tot>=q[it].d){
					for(int i=0;i<l1;i++)ADD[i]=ls[ADD[i]];
					for(int i=0;i<l2;i++)MINUS[i]=ls[MINUS[i]];
					r=(l+r)>>1;
				}else{
					q[it].d-=tot;
					for(int i=0;i<l1;i++)ADD[i]=rs[ADD[i]];
					for(int i=0;i<l2;i++)MINUS[i]=rs[MINUS[i]];
					l=(l+r)>>1,l++;
				}
			}
			printf("%d\n",busket[l]);
		}
	}
	return 0;
}

平衡树

平衡树一般用作处理序列问题

BST

满足任意节点左子树权值小于它,右子树权值大于它
显然其中序遍历有序,每一棵子树就是一个键值区间
定义一些基本操作

  • 查找:根据 BST 性质直接比对即可

  • 插入:与查找基本相同,可以递归传引用直接处理
    但一般为了平衡还会有其他操作

  • 取最大最小:一直向左/右递归即可

  • 取前驱后继:容易证明,若前驱/后继存在,则一定在当前节点到根的一条链上
    当前节点第一次作为左儿子时的父亲即为后继,前驱同理

    以后继为例
    显然如果当前节点是右儿子,其后继一定并非其父亲或其兄弟左子树中任意节点
    而如果当前节点是左儿子,右子树中节点显然没有其父亲优

  • 删除:这个很复杂,因具体形态而异

  • 取排名:查找过程中记录 rank

  • 根据排名查询:二分递归/迭代

显然,BST 在极端数据下无法保证时间复杂度

有旋 Treap

Treap 在朴素 BST 的基础上加上了一个随机优先级
想要维护这个随机键值满足小根堆性质

缺点:
不可持久化
不适于序列问题
理解难度较高
书写难度较大
引用范围窄

优点:

  • 节点设定:相同权值节点作为一个,记录 cnt

  • 建树(对于有初始序列):
    对于键值排序
    暴力找到键值最小位置
    左右递归建树 \(O(n\log n)\)

    显然这样和暴力插入没有任何区别
    有一种更快的 \(O(n)\) 建树方法
    先看一下这些图:Link
    思路就是先把所有数按键值排序
    先小后大一个一个插入
    考虑如果当前随机值于栈顶就去当栈顶的右儿子,否则一直弹出栈顶
    此时刚才最后一个弹出来的就成了栈顶左儿子

    stack<int> st;
    for(int i=1,last;i<=n;i++){
        last=0;
        while(!st.empty()&&rnd[st.top()]>rnd[i]) last=st.top(),st.pop();
        if(!st.empty()) s[st.top()][1]=i; s[i][0]=last;
    }
    

    看着可以,问题是什么时候 Update 呢?
    这个我没有太好的方法
    可行解:用刚才的 stack 跑一遍 dfs

  • 旋转:分为左旋和右旋

    发现旋转后并未破坏原本的 BST 性质
    唯一作用是把当前节点的左/右儿子转上来
    主要是在插入/删除时通过旋转调整堆性质

    int s[N][2]; 
    void Rotate(int &u,bool k){int tmp=s[u][k]; s[u][k]=s[tmp][k^1],s[tmp][k^1]=u,u=tmp;}
    
  • 插入:同 BST
    只会在不满足 rnd 性质时进行调整

    void Insert(int &u,int k){
        if(!u){new_node(u,k); return;}
        if(k==num[u]){++cnt[u]; return;}
        bool tmp=(k>num[u]);
        insert(s[u][tmp],k);
        if(rnd[u]>rnd[s[u][tmp]]) rotate(u,tmp);
        pu(u);
    }
    
  • 删除:分类讨论
    先查找当前节点判断是否需要删除(还是仅仅将 cnt 减一)
    儿子不全用儿子
    儿子齐全就把 rnd 较小的转上来并递归删除

    void Delete(int &u,int x){
        if(k[u]!=x){Delete(s[u][x>k[u]],x),Update(u); return;}
        if(cnt[u]>1){--cnt[u],--big[u]; return;}
        if(s[u][0]==0||s[u][1]==0){u=s[u][0]+s[u][1]; return;}
        bool tmp=(r[s[u][1]]<r[s[u][0]]); Rotate(u,tmp),Delete(s[u][tmp^1],x),Update(u);
    }
    
  • 其余操作同 BST
    对于有些题目需要在找不到前驱/后继时特殊处理
    一般会插两个虚点进去

Code 204 ms

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+7;
mt19937 Rand(114514);
int k[N],r[N],cnt[N],big[N],s[N][2],rt,CNT;
inline void dfs(int u){
	if(!u) return;
	dfs(s[u][0]),printf("%d ",k[u]),dfs(s[u][1]);
}
inline void New(int &u,int x){u=++CNT,k[u]=x,r[u]=Rand(),cnt[u]=big[u]=1;}
inline void Update(int u){big[u]=big[s[u][0]]+big[s[u][1]]+cnt[u];}
inline void Rotate(int &u,bool x){int tmp=s[u][x]; s[u][x]=s[tmp][x^1],s[tmp][x^1]=u,Update(u),Update(tmp),u=tmp;}
void Insert(int &u,int x){
    if(!u){New(u,x); return;} if(k[u]==x){++cnt[u],++big[u]; return;}
    bool tmp=(x>k[u]); Insert(s[u][tmp],x),Update(u); 
	if(r[s[u][tmp]]<r[u]) Rotate(u,tmp);
}
void Delete(int &u,int x){
    if(k[u]!=x){Delete(s[u][x>k[u]],x),Update(u); return;}
    if(cnt[u]>1){--cnt[u],--big[u]; return;}
    if(s[u][0]==0||s[u][1]==0){u=s[u][0]+s[u][1]; return;}
    bool tmp=(r[s[u][1]]<r[s[u][0]]); Rotate(u,tmp),Delete(s[u][tmp^1],x),Update(u);
}
int Rank(int x){
    int u=rt,ans=0;
    while(u) if(k[u]<x) ans+=big[s[u][0]]+cnt[u],u=s[u][1]; else u=s[u][0];
    return ans;
}
int Kth(int x){
    int u=rt;
    while(true) if(big[s[u][0]]>=x) u=s[u][0]; else if(big[s[u][0]]+cnt[u]<x) x-=big[s[u][0]]+cnt[u],u=s[u][1]; else return k[u];
}
int Pre(int x){
    int tmp=INT_MIN,u=rt;
    while(u) if(k[u]<x) tmp=k[u],u=s[u][1]; else u=s[u][0];
    return tmp;
}
int Nxt(int x){
    int tmp=INT_MAX,u=rt;
    while(u) if(k[u]>x) tmp=k[u],u=s[u][0]; else u=s[u][1];
    return tmp;
}
int main(){
    int T,opt,x; scanf("%d",&T);
    while(T--){
        scanf("%d%d",&opt,&x);
        if(opt==1) Insert(rt,x);
        else if(opt==2) Delete(rt,x);
        else if(opt==3) printf("%d\n",Rank(x)+1);
        else if(opt==4) printf("%d\n",Kth(x));
        else if(opt==5) printf("%d\n",Pre(x));
        else if(opt==6) printf("%d\n",Nxt(x));
    }
    return 0;
}
无旋 Treap

满足 Treap 定义

优点:
思维难度小
好写
支持功能多
可持久化
使用广

缺点:
码量稍大

  • 按值分裂:传入当前的根和分裂值,传出两个分裂好的根
    只需要判断一下当前根和分裂值的关系
    按排名分裂同理

    void Split(int u,int x,int &l,int &r){if(num[u]<=x) l=u,Split(rs[u],x,rs[u],r); else r=u,Split(ls[u],x,l,ls[u]);}
    
  • 合并:合并两颗 Treap,返回他们的根
    其中左子树中所有元素小于右子树

    int Merge(int l,int r){
        if(l==0||r==0) return l+r;
        if(rnd[l]<rnd[r]) {rs[l]=Merge(rs[l],r),Update(l); return l;} else {ls[r]=Merge(l,ls[r]),Update(r); return r;}
    }
    
  • 插入

    Split(rt,x,l,r),rt=Merge(Merge(l,New(x)),r);
    
  • 删除
    删除所有很简单

    Split(rt,x,l,r),Split(l,x-1,L,R),rt=Merge(L,r);
    

    删除一个就需要一些技巧

    void Delete_single(int &i){
        if(!i) return;
        if(ls[i]==0&&rs[i]==0) i=0;
        if(ls[i]) Delete_single(ls[i]),Update(i); else Delete_single(rs[i]),Update(i);
    }
    
    Split(rt,x,l,r),Split(l,x-1,L,R),Delete_single(R),rt=Merge(Merge(L,R),r);
    

    upd
    可以 Split(rt,x,l,r),Split(l,x-1,L,R),rt=Merge(Merge(ls[R],rs[R]),r);
    我纯唐氏

  • 其余操作同 BST
    注意在 FHQ 中一般一个点只存一个值

Code 274 ms 一把过
我再也不说他码量大了

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+7;
mt19937 Rand(114514);
int num[N],cnt[N],ls[N],rs[N],rnd[N],CNT,rt;
int New(int k){num[++CNT]=k,cnt[CNT]=1,rnd[CNT]=Rand(); return CNT;}
void Update(int i){cnt[i]=cnt[ls[i]]+cnt[rs[i]]+1;}
int Merge(int l,int r){
    if(l==0||r==0) return l+r;
    if(rnd[l]<rnd[r]){rs[l]=Merge(rs[l],r),Update(l); return l;} else {ls[r]=Merge(l,ls[r]),Update(r); return r;}
}
void Split(int u,int x,int &l,int &r){if(u==0){l=r=0; return;} if(num[u]<=x) l=u,Split(rs[u],x,rs[u],r),Update(l); else r=u,Split(ls[u],x,l,ls[u]),Update(r);}
void Split2(int u,int x,int &l,int &r){
    if(u==0){l=r=0; return;} 
    if(cnt[ls[u]]+1<=x)l=u,Split2(rs[u],x-cnt[ls[u]]-1,rs[u],r),Update(l); else r=u,Split2(ls[u],x,l,ls[u]),Update(r);
}
void Delete_single(int &i){
    if(ls[i]==0&&rs[i]==0) {i=0; return;}
    if(ls[i]) Delete_single(ls[i]); else Delete_single(rs[i]); Update(i);
}
int Max(int i){while(rs[i]) i=rs[i]; return num[i];}
int Min(int i){while(ls[i]) i=ls[i]; return num[i];}
int main(){
    int T,opt,x,l,r,L,R; scanf("%d",&T);
    while(T--){
        scanf("%d%d",&opt,&x);
        if(opt==1) Split(rt,x,L,R),rt=Merge(Merge(L,New(x)),R);
        else if(opt==2) Split(rt,x,L,R),Split(L,x-1,l,r),Delete_single(r),rt=Merge(Merge(l,r),R);
        else if(opt==3) Split(rt,x-1,L,R),printf("%d\n",cnt[L]+1),rt=Merge(L,R);
        else if(opt==4) Split2(rt,x,L,R),printf("%d\n",Max(L)),rt=Merge(L,R);
        else if(opt==5) Split(rt,x-1,L,R),printf("%d\n",Max(L)),rt=Merge(L,R);
        else if(opt==6) Split(rt,x,L,R),printf("%d\n",Min(R)),rt=Merge(L,R);
    }
    return 0;
}
平衡树区间操作
经典例题之区间反转

直接用下标当平衡树中键值
也像线段树一样放 tag
但要注意进行翻转的顺序

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+6;
mt19937 Rand(114514); int n,cnt,rt; struct node{int num,rnd,ls,rs,cnt=0; bool rev;}k[N];
int New(int num){++cnt; k[cnt].num=num,k[cnt].rnd=Rand(),k[cnt].cnt=1; return cnt;}
void Update(int i){k[i].cnt=1+k[k[i].ls].cnt+k[k[i].rs].cnt;}
void Build(int &u,int l,int r){
    if(l>r){u=0; return;} u=l; if(l==r) return;
    for(int i=l+1;i<=r;i++) if(k[i].rnd<k[u].rnd) u=i;
    Build(k[u].ls,l,u-1),Build(k[u].rs,u+1,r),Update(u);
}
void pd(int i){
	if(k[i].rev==0) return; k[i].rev=0;
	swap(k[i].ls,k[i].rs);
	k[k[i].ls].rev^=1,k[k[i].rs].rev^=1;
}
int Merge(int l,int r){
    if(l==0||r==0) return l+r;
    if(k[r].rnd>k[l].rnd){pd(l),k[l].rs=Merge(k[l].rs,r),Update(l); return l;} else {pd(r),k[r].ls=Merge(l,k[r].ls),Update(r); return r;}
}
void Split(int i,int x,int &l,int &r){
	if(!i){l=r=0; return;} pd(i);
	if(x>=k[k[i].ls].cnt+1) l=i,Split(k[i].rs,x-k[k[i].ls].cnt-1,k[i].rs,r);
	else r=i,Split(k[i].ls,x,l,k[i].ls);
	Update(i);
}
void dfs(int i){
	if(!i) return; pd(i);
	dfs(k[i].ls),printf("%d ",k[i].num),dfs(k[i].rs);
} 
int main(){
    int T,l,r,L,R,x,y; scanf("%d%d",&n,&T); for(int i=1;i<=n;i++) New(i); Build(rt,1,n);
    while(T--){
    	scanf("%d%d",&x,&y),Split(rt,x-1,L,R),Split(R,y-x+1,l,r),k[l].rev^=1;
    	rt=Merge(L,Merge(l,r));
	}
	dfs(rt),putchar('\n');
    return 0;
}
P4008

思考快速删除不难
快速插入可以直接建树再合并
移动直接 \(O(1)\)
输出的话考虑分裂出来中序遍历
由于不是 Leafy-Tree 显然 \(O(n)\)
综上可过

#include<bits/stdc++.h>
using namespace std;
const int N=3e6; mt19937 rd(114514); char val[N],tmp[N]; int ls[N],rs[N],cnt[N],rnd[N],idx,cur,rt; stack<int> st;
inline void New(char x){val[++idx]=x,cnt[idx]=1,rnd[idx]=rd();}
inline void Pu(int i){cnt[i]=1+cnt[ls[i]]+cnt[rs[i]];}
void Upddfs(int i){if(!i)return; Upddfs(ls[i]),Upddfs(rs[i]),Pu(i);}
inline int Build(char *x){
    int len=strlen(x),_rt=0; 
    for(int i=1,tmp;i<=len;i++){
        tmp=0,New(x[i-1]);
        while(!st.empty()&&rnd[st.top()]>rnd[idx]) tmp=st.top(),st.pop();
        if(!st.empty()) rs[st.top()]=idx; else _rt=idx; ls[idx]=tmp,st.push(idx);
    }
    Upddfs(_rt);
    while(!st.empty()) st.pop();
    return _rt;
}
int Merge(int l,int r){
//	printf("Merge %d %d\n",l,r);
    if(l==0||r==0) return l+r;
    if(rnd[l]<rnd[r]) {rs[l]=Merge(rs[l],r),Pu(l); return l;}
    else {ls[r]=Merge(l,ls[r]),Pu(r); return r;}
}
void Splitrk(int i,int x,int &l,int &r){
//	printf("Split %d %d\n",i,x);
    if(i==0){l=r=0; return;}
    if(x>=cnt[ls[i]]+1) l=i,Splitrk(rs[i],x-cnt[ls[i]]-1,rs[i],r);
    else r=i,Splitrk(ls[i],x,l,ls[i]);
    Pu(i);
}
void Dfs(int i){if(!i) return; Dfs(ls[i]),putchar(val[i]),Dfs(rs[i]);}
void read(int len){
    char w;
    for(int i=0;i<len;i++){
        w=getchar();
        while(w=='\n'||w<32||w>126) w=getchar();
        tmp[i]=w;
    }
}
int main(){
    int T,l,L,R,LL,RR; char opt[24]; scanf("%d",&T);
    while(T--){
        scanf("%s",opt);
        switch(opt[0]){
            case 'I':
                memset(tmp,0,sizeof tmp),scanf("%d",&l),read(l),l=Build(tmp),Splitrk(rt,cur,LL,RR),rt=Merge(Merge(LL,l),RR);break;
            case 'M':scanf("%d",&l),cur=l;break;
            case 'D':scanf("%d",&l),Splitrk(rt,cur,LL,RR),Splitrk(RR,l,L,R),rt=Merge(LL,R);break;
            case 'G':scanf("%d",&l),Splitrk(rt,cur,LL,RR),Splitrk(RR,l,L,R),Dfs(L),putchar('\n'),rt=Merge(LL,Merge(L,R));break;
            case 'P':--cur;break;
            case 'N':++cur;break;
        }
    }
    return 0;
}
P7739

注意到 \(a_i+\frac 1{\frac xy}=\frac{a_ix+y}{x}\)
则用一个矩阵表示这个东西
显然一二矩阵右乘二二矩阵可行

\[\begin{bmatrix} x & y \end{bmatrix} * \begin{bmatrix} a_i & 1\\ 1 & 0\\ \end{bmatrix} = \begin{bmatrix} a_ix+y & x \end{bmatrix} \]

那么对于 \(C\) 就是把 \(\begin{bmatrix} a_i & 1\\ 1 & 0\\\end{bmatrix}\) 变成 \(\begin{bmatrix} a_i+1 & 1\\ 1 & 0\\\end{bmatrix}\) 只需乘 \(\begin{bmatrix} 1 & 0\\ 1 & 1\\\end{bmatrix}\)

对于 \(W\) 操作如果最后不是一就是 \(\begin{bmatrix} 1 & 0\\ -1 & 1\\\end{bmatrix}*\begin{bmatrix} 1 & 0\\ 1 & 1\\\end{bmatrix}*\begin{bmatrix} 1 & 0\\ 1 & 1\\\end{bmatrix}=\begin{bmatrix} 1 & 0\\ 0 & 1\\\end{bmatrix}*\begin{bmatrix} 1 & 0\\ 1 & 1\\\end{bmatrix}=\begin{bmatrix} 1 & 0\\ 1 & 1\\\end{bmatrix}\)
否则就是

posted @ 2025-07-24 20:44  2025ing  阅读(12)  评论(0)    收藏  举报