分块+点分治+Treap
byWYCby\ WYCby WYC
Part1 分块
概念
就是将nnn个数分成若干个块,然后要处理的时候整块一起的加上局部的直接暴力。
如果将块的大小分配好一般每次都是O(n)O(\sqrt n)O(n)的。
而且因为十分暴力,所以有很多优秀的性质。
实现方法
怎么暴力的还用讲实现方法,好吧,讲一些基础的
要求区间求和,区间修改
对于每个块维护一个所以值的和和一个懒惰
区间求和时,将整块的直接加上去,局部的暴力加上去
区间修改时,将整块的懒惰标记,局部的暴力加上去
对于局部修改时,如果有懒惰就直接暴力将整个块都修改,然后去掉懒惰。
时间复杂度每个都是O(n)O(\sqrt n)O(n)
是不是十分优秀
例题
在例题中体现优秀的性质
P4168-[Violet]蒲公英
题目大意
询问区间众数
解题思路
将数字离散化,然后分块。对于数组vi,j,kv_{i,j,k}vi,j,k,表示i∼ji\sim ji∼j个块,kkk的个数。对于询问(l,r)(l,r)(l,r),将整块的直接累计,然后局部的直接暴力。
时间复杂度:O(NT2+MNT)O(NT^2+\frac{MN}{T})O(NT2+TMN),根据LYD的说法,让T≈n3T\approx \sqrt[3]nT≈3n的话,时间复杂度就可以在O(N53)O(N^{\frac{5}{3}})O(N35)级别。
代码写优美些或者加点优化就可以过。
code
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#define Tn 40
#define N 40010
using namespace std;
int n,m,L[Tn],R[Tn],v[Tn][Tn][N],l,r,num[N];
int z[N],w[N],a[N],cnt,k[N],t,T,pos[N],x;
bool cmp(int x,int y)//排序
{return z[x]<z[y];}
void begins()//预处理
{for(int i=1;i<=t;i++){L[i]=(i-1)*T+1;R[i]=i*T;}if(R[t]<n) t++,L[t]=R[t-1]+1,R[t]=n;//计算边界for(int i=1;i<=t;i++)for(int j=i;j<=t;j++)for(int k=L[i];k<=R[j];k++)v[i][j][a[k]]++;//计算v数组
}
int ask(int l,int r)
{int p=pos[l],q=pos[r];if(p-q<=2){memset(k,0,sizeof(k));for(int i=l;i<=r;i++)k[a[i]]++;}else{p++;q--;for(int i=1;i<=cnt;i++)k[i]=v[p][q][i];//直接累计for(int i=l;i<L[p];i++)//暴力统计k[a[i]]++;for(int i=R[q]+1;i<=r;i++)//暴力统计*2k[a[i]]++;}int mark=1;for(int i=2;i<=cnt;i++)//找众数if(k[i]>k[mark]) mark=i;return w[mark];
}
int main()
{scanf("%d%d",&n,&m);t=(int)pow(double(n),1.0/3);T=n/t;for(int i=1;i<=n;i++){scanf("%d",&z[i]);num[i]=i;}sort(num+1,num+1+n,cmp);z[0]=-1;for(int i=1;i<=n;i++)//离散化{if(z[num[i]]!=z[num[i-1]]) cnt++,w[cnt]=z[num[i]];;a[num[i]]=cnt;}T=n/t;begins();for(int i=1;i<=m;i++){scanf("%d%d",&l,&r);l=(l+x-1)%n+1;r=(r+x-1)%n+1;if(l>r) swap(l,r);printf("%d\n",x=ask(l,r));}
}
优秀性质1
对于块你可以构建二维的,就十分优秀。
如这道题中vi,j,kv_{i,j,k}vi,j,k对于块进行了二维
P4879-ycz的妹子
题目大意
有若干种操作
- Cxy:ax−=yC\ x\ y:a_x-=yC x y:ax−=y
- Ixy:I\ x\ y:I x y:加一个(原本有的话就改变)ax=ya_x=yax=y
- Q:Q:Q:询问所以数的和
- Dx:D\ x:D x:删除第xxx个(有的才算)数。
解题思路
我们发现只有单点修改,和全部求和。所以ansansans表示全部的和,然后前三个操作就搞定了。
第四个操作维护一个分块,记录每块有的数的个数,然后找到目标在那个块,之后这个块里暴力寻找,这个操作时间负责度O(n)O(\sqrt n)O(n)
总体时间复杂度:O(nn)O(n\sqrt n)O(nn)
codecodecode
#include<cstdio>
#include<cmath>
#include<iostream>
#define ll long long
#define N 500000
#define T 1000
using namespace std;
ll n,m,a[N+10],ans,no[N+10],x,y,t;
ll L[T],R[T],num[T],pos[N+10];
char c;
int main()
{scanf("%lld%lld",&n,&m);t=sqrt(N);for(ll i=1;i<=N;i++){pos[i]=(i-1)/t+1;if(i>n)no[i]=1;}for(ll i=1;i<=n;i++){scanf("%lld",&a[i]);num[pos[i]]++;ans+=a[i];}for(ll i=1;i<=m;i++){cin>>c;if(c=='Q')printf("%lld\n",ans);else if(c=='I'){scanf("%lld%lld",&x,&y);ans-=a[x];a[x]=y;ans+=y;n=max(n,x);if(no[x]){no[x]=0;num[pos[x]]++;}}else if(c=='C'){scanf("%lld%lld",&x,&y);if(no[x]) continue;ans-=y;a[x]-=y;}else if(c=='D'){scanf("%lld",&x);ll k=1;while(x-num[k]>0)x-=num[k],k++;for(int i=(k-1)*t+1;i<=min(k*t,n);i++){if(!no[i]) --x;if(!x){num[k]--;no[i]=1;ans-=a[i];a[i]=0;break;}}}}
}
优秀性质2
一个块一个块的跑,利用储存块的信息快速锁定目标
P3203-[HNOI2010]弹飞绵羊
这个就强大了
题目大意
nnn个装置。到第iii个装置会被往前弹aia_iai个。
两种操作
修改aia_iai和询问从iii出发要多少次弹射可以弹出去。
解题思路
first,本题正解LCT。然而分块可以轻松过掉
分块。对于每个点,维护要多少步弹出该块和弹出去后弹到哪里。
询问就直接根据两个数据,修改就直接重构整个块。
时间复杂度:O(nn)O(n\sqrt n)O(nn)
code
#include<cstdio>
#include<cmath>
#define N 200010
#define T 500
using namespace std;
int n,m,x,t,a[N],L[T],R[T],step[N],to[N],pos[N];
void pre_work()//预处理
{for(int i=1;i<=t;i++)//块边界{L[i]=(i-1)*t+1;R[i]=i*t;}if(R[t]!=n) t++,L[t]=R[t-1]+1,R[t]=n;for(int i=1;i<=t;i++)for(int j=R[i];j>=L[i];j--){if(j+a[j]<=R[i]) step[j]=step[j+a[j]]+1,to[j]=to[j+a[j]];else step[j]=1,to[j]=j+a[j];pos[j]=i;}//初始数据
}
int ask(int x)//询问
{int ans=0;while(x<=n)ans+=step[x],x=to[x];return ans;
}
void change(int i)//重构块
{for(int j=R[i];j>=L[i];j--)if(j+a[j]<=R[i]) step[j]=step[j+a[j]]+1,to[j]=to[j+a[j]];else step[j]=1,to[j]=j+a[j];
}
int main()
{scanf("%d",&n);t=sqrt(n);for(int i=1;i<=n;i++)scanf("%d",&a[i]);pre_work();scanf("%d",&m);for(int i=1;i<=m;i++){scanf("%d",&x);if(x==1){scanf("%d",&x);x++;printf("%d\n",ask(x));}else{scanf("%d",&x);x++;scanf("%d",&a[x]);change(pos[x]);}}
}
优秀性质3
本题最强大的地方是在于当他修改时,直接暴力重构了整个块,十分强大
性质总结
对于分块,最强大的操作就是暴力。因为无论怎么暴力,只要每次保证不用重构太多块,基本都是O(n)O(\sqrt n)O(n)的
然后就算根据每个块的信息找一遍,也依据是O(n)O(\sqrt n)O(n)的
而且n2=n\sqrt n^2=nn2=n所以我们可以猥琐欲为的将数据当做可以n2n^2n2的来看
分块-终章
“局部暴力,大段维护”。当然分块十分简单,性质也十分优秀,也很好写。
but!but!but!,时间复杂度终究是O(nn)O(n\sqrt n)O(nn)的,如果出题人让你用O(nlogn)O(n\ log\ n)O(n log n)也没有办法
还是老老实实写别的数据结构对吧
Part2-点分治
概念
首先,点分治无论在速度还是处理问题方面都会被树上启发式合并吊打,而且我也不知道为什么他会被分进数据结构里,但是不重要!
点分治在树上进行分治,每次将树分治成若干棵不同的子树处理。
实现方法
一般用于处理树上路径长度的问题
这里举LuoguLuoguLuogu的模板P3806P3806P3806
一棵树(无向),mmm个询问求长度为kkk的路径存不存在。
对于以ppp为根的子树
然后考虑路径种类,其实只有两种
- 经过根节点
- 包含在某一棵子树中
对于1,我们将路径分成两半.
求出根节点里每个点的距离did_idi,和每个节点位于那个子树bib_ibi
要求满足bx≠byb_x\neq b_ybx=by和dx+dy=kd_x+d_y=kdx+dy=k
这时候定义函数Calc(p)Calc(p)Calc(p)表示以ppp为根的子树上上述路径的条数
而路径二就直接分治让子树再处理一遍就好了。
现在是考虑Calc(p)Calc(p)Calc(p)如何定义的问题。
因为数据很小,用lonxlon_xlonx表示是否存在长度为xxx的路径
然后我们对于每颗子树,先用之前的lonlonlon计算一下询问
之后在将这棵子树加入lonlonlon数组
然后在子树之中继续处理
时间复杂度:O(TNlogN)O(TN\ log \ N)O(TN log N),TTT为树的层数
可是如果树是一条链,那么时间就会退化为O(N2logN)O(N^2\ log\ N)O(N2 log N)
但是如果我们每次以子树的重心作为新分治的根,那么最多只会有logNlog\ Nlog N层。
时间就可以稳在O(Nlog2N)O(N\ log^2\ N)O(N log2 N)
Part 3:Treap
概念
TreapTreapTreap就是平衡树的一种,所谓平衡树就是要求稳定在lognlog\ nlog n层左右的BSTBSTBST,为什么要稳定层数,我们等会再讲。
实现方法
将TreapTreapTreap的实现方法之前要先将BSTBSTBST
BSTBSTBST
所谓BST,就是二叉查找树。
二叉查找树有个优秀的性质,那就是对于每个节点,左子节点的权值比它小,右子节点比它大。
这样我们就可以愉快的干很多事,比如查找前驱,后继,排名啊
明人不说暗话,先来将实现方法
开始先构建两个节点−inf-inf−inf和infinfinf,以任意一个为根
int New(int new_val){val[++tot]=new_val;dat[tot]=rand();cnt[tot]=size[tot]=1;return tot;}void Build(){New(-INF);New(INF);root=1;r[1]=2;Updata(root);}
1[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I4PJVqvd-1576912899795)(https://i.loli.net/2019/01/20/5c446542aac73.png)]
插入andandand删除:
插入一个数时,先从根开始。对于走到的每一个点,如果比它小就往左,不然就往右走。知道找到一个空余的位置就插入。
void Insert(int &p,int num){if(p==0){p=New(num);return;}//如果有空位了if(num==val[p]){cnt[p]++;Updata(p);return;}//已经有这个数了if(num<val[p]){Insert(l[p],num);}//小于往左else{Insert(r[p],num);}//大于往右Updata(p);//更新数据}
删除也是根据那个走法找到位置,然后删除
删除时注意将子节点接上去
void Remove(int val){int &p=root;while(p){if(val==a[p].val) break;p=val<a[p].val?a[p].l:a[p].r;}if(p==0) return;if(a[p].l==0){p=a[p].r}//没左接右else if(a[p].r==0){p=a[p].l;}//没右接左else{//求个后继int next=a[p].r;while(a[next].l>0) next=a[next].l;//next没有左子树,直接删除Romove(a[next].val);//令节点next代替节点p的位置a[next].l=a[p].r;a[next].r=a[p].r;p=next;}
}
求前驱/后继
valvalval后继就是比valvalval大的最小的数。
对于求后继,我们可以用一步一步往下走的方法,并在路上更新答案ansansans,直到走到valvalval的位置或结束时。
这时有3种结果
- 没有valvalval这个节点。那就直接取ansansans
- 有valvalval,但没有右子树。也是直接取ansansans
- 找到valvalval,且也有右子树。走到右边,然后一直往左走,就是valvalval的后继
前驱同理
int GetPre(int num){int ans=1;int p=root;while(p){if(num==val[p]){//找到val节点if(l[p]>0){p=l[p];//往左一步while(r[p]>0) p=r[p];//一直往右ans=p;//统计}break;}if(val[p]<num&&val[p]>val[ans]) ans=p;p=num<val[p]?l[p]:r[p];//走}return val[ans];}int GetNext(int num){int ans=2;int p=root;while(p){if(num==val[p]){if(r[p]>0){p=r[p];while(l[p]>0) p=l[p];ans=p;}break;}if(val[p]>num&&val[p]<val[ans]) ans=p;p=num<val[p]?l[p]:r[p];}return val[ans];}
查询一个数的排名/一个排名的数
一个数的排名就直接走到那个点,顺路根据树的大小统计前面有多少个点就好了。
一个排名的数,就根据子树大小来走就ok了。
int GetRankByVal(int p,int num){if(p==0) return 0;if(num==val[p]) return size[l[p]]+1;if(num<val[p]) return GetRankByVal(l[p],num);return GetRankByVal(r[p],num)+size[l[p]]+cnt[p];}int GetValByRank(int p,int rank){if (p==0) return INF;if(size[l[p]]>=rank) return GetValByRank(l[p],rank);if(size[l[p]]+cnt[p]>=rank) return val[p];return GetValByRank(r[p],rank-size[l[p]]-cnt[p]);}
这就是BSTBSTBST的全部了。我们来考虑它的弊端。
每次时间就会为O(T)O(T)O(T)
如果我以单调的插入数,那么每次就会退化为O(N)O(N)O(N)
那么我们就要让这个BSTBSTBST平衡
TreapTreapTreap
TreapTreapTreap是其中一种让BSTBSTBST保持在lognlog\ nlog n层左右的一个数据结构
具体实现方法就是通过旋转,对于一个东西
可以旋转为
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dTwPYOyz-1576912899796)(https://i.loli.net/2019/01/20/5c446ed9d8f17.png)]
总之就是分为左旋和右旋
void zig(int &p){int q=l[p];l[p]=r[q];r[q]=p;p=q;Updata(r[p]);Updata(p);}void zag(int &p){int q=r[p];r[p]=l[q];l[q]=p;p=q;Updata(l[p]);Updata(p);}
只会我们对每个点,我们定义一个特征值。然后我们通过旋转让特征值满足二叉堆的性质。
只要让特征值时随机的,基本都可以维持在lognlog\ nlog n层附近。
好了,放codecodecode
#include<cstdio>
#include<algorithm>
#define INF 2147483647/3
#define N 100010
using namespace std;
int n,root;
struct Treap_node{int l[N],r[N];int val[N],dat[N];int cnt[N],size[N];int tot;int New(int new_val){val[++tot]=new_val;dat[tot]=rand();cnt[tot]=size[tot]=1;return tot;}void Updata(int p){size[p]=size[l[p]]+size[r[p]]+cnt[p];}void Build(){New(-INF);New(INF);root=1;r[1]=2;Updata(root);}int GetRankByVal(int p,int num){if(p==0) return 0;if(num==val[p]) return size[l[p]]+1;if(num<val[p]) return GetRankByVal(l[p],num);return GetRankByVal(r[p],num)+size[l[p]]+cnt[p];}int GetValByRank(int p,int rank){if (p==0) return INF;if(size[l[p]]>=rank) return GetValByRank(l[p],rank);if(size[l[p]]+cnt[p]>=rank) return val[p];return GetValByRank(r[p],rank-size[l[p]]-cnt[p]);}void zig(int &p){int q=l[p];l[p]=r[q];r[q]=p;p=q;Updata(r[p]);Updata(p);}void zag(int &p){int q=r[p];r[p]=l[q];l[q]=p;p=q;Updata(l[p]);Updata(p);}void Insert(int &p,int num){if(p==0){p=New(num);return;}if(num==val[p]){cnt[p]++;Updata(p);return;}if(num<val[p]){Insert(l[p],num);if(dat[p]<dat[l[p]]) zig(p);}else{Insert(r[p],num);if(dat[p]<dat[r[p]]) zag(p);}Updata(p);}int GetPre(int num){int ans=1;int p=root;while(p){if(num==val[p]){if(l[p]>0){p=l[p];while(r[p]>0) p=r[p];ans=p;}break;}if(val[p]<num&&val[p]>val[ans]) ans=p;p=num<val[p]?l[p]:r[p];}return val[ans];}int GetNext(int num){int ans=2;int p=root;while(p){if(num==val[p]){if(r[p]>0){p=r[p];while(l[p]>0) p=l[p];ans=p;}break;}if(val[p]>num&&val[p]<val[ans]) ans=p;p=num<val[p]?l[p]:r[p];}return val[ans];}void Remove(int &p,int num){if(p==0) return;if(num==val[p]){if(cnt[p]>1){cnt[p]--;Updata(p);return;}if(l[p]||r[p]){if(r[p]==0||dat[l[p]]>dat[r[p]])zig(p),Remove(r[p],num);elsezag(p),Remove(l[p],num);Updata(p);}else p=0;return;}num<val[p]?Remove(l[p],num):Remove(r[p],num);Updata(p);}
}a;
int main()
{a.Build();scanf("%d",&n);while(n--){int opt,x;scanf("%d%d",&opt,&x);switch(opt){case 1:a.Insert(root,x);break;case 2:a.Remove(root,x);break;case 3:printf("%d\n",a.GetRankByVal(root,x)-1);break;case 4:printf("%d\n",a.GetValByRank(root,x+1));break;case 5:printf("%d\n",a.GetPre(x));break;case 6:printf("%d\n",a.GetNext(x));break;}}
}
虽然基本都可以用setsetset搞定,但是还是可以做很多NBNBNB的好东西的。
Part4:树链剖分
概念
将树分割成若干条链来处理,将树上的问题转换为线性问题
实现方法
实现概念
学树链剖分之前我们先来getgetget一些前置知识
重儿子:子树最大的一个儿子
轻儿子:其他儿子
重边:连接重儿子的边
轻边:其他边
重路径:重边的路径
还有一个重要性质:从根到任何一个点的轻边数不超过lognlog\ nlog n条
证明:
若走到一条轻边,那么子树大小至少缩小一半,那么最多走logn\log nlogn条轻边子树大小就会缩小为1
证毕
所以我们对于一条路径的轻边数量也是logloglog级的。
所以我们可以对于重路径将树进行划分。
我们来看例题:
要求支持子树修改,子树求和,路径修改,路径求和。
对于子树操作很简单,我们按照dfsdfsdfs序加入线段树,然后线段树区间修改就可以做到子树修改,可以路径问题如何解决?
这时候我们就可以用树链剖分了。我们考虑将dfsdfsdfs修改一下。
我们可以每次优先走重儿子,那么重路径在线段树上的位置就是连续的,对于一段重路径我们就可以进行区间修改了,而轻边有不会太多,所以一次操作时间复杂度是O(log2n)O(log^2\ n)O(log2 n)的
代码实现
我们先跑两次dfsdfsdfs预处理
第一遍:
sonx:xson_x:xsonx:x的重儿子
depx:xdep_x:xdepx:x的深度
sizx:xsiz_x:xsizx:x的子树大小
fx:xf_x:xfx:x的父节点
第二遍:
segx:xseg_x:xsegx:x在线段树上的位置
idz:id_z:idz:线段树zzz位置上的节点
topx:xtop_x:xtopx:x所在重路径最浅的节点
之后我们就预处理完了。
之后我们对于路径操作可以向倍增求LCALCALCA一样,将一条路径拆分为两条路径,不过变为每次跳到topxtop_xtopx,然后跳的过程重进行用线段树进行区间修改。
上
codecodecode
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=200000;
int tot,cnt,n,m,s,p,ls[N],pw[N],id[N];
int siz[N],dep[N],f[N],son[N],seg[N],top[N];
struct treenode{int l,r,lazy,val;
};
struct LineTree{treenode t[N*2];void build(int x,int l,int r){t[x].l=l;t[x].r=r;if(l==r){t[x].val=pw[id[l]]%p;return;}int mid=(l+r)>>1;build(x*2,l,mid);build(x*2+1,mid+1,r);t[x].val=(t[x*2].val+t[x*2+1].val)%p;}void downdata(int x){if(t[x].lazy){(t[x*2].lazy+=t[x].lazy)%=p;(t[x*2].val+=t[x].lazy*(t[x*2].r-t[x*2].l+1))%=p;(t[x*2+1].lazy+=t[x].lazy)%=p;(t[x*2+1].val+=t[x].lazy*(t[x*2+1].r-t[x*2+1].l+1))%=p;t[x].lazy=0;}}void change(int x,int l,int r,int num){if(t[x].l==l&&t[x].r==r){(t[x].val+=num*(t[x].r-t[x].l+1))%=p;(t[x].lazy+=num)%=p;return;}downdata(x);int mid=(t[x].l+t[x].r)>>1;if(r<=mid) change(x*2,l,r,num);else if(l>mid) change(x*2+1,l,r,num);else change(x*2,l,mid,num),change(x*2+1,mid+1,r,num);t[x].val=(t[x*2].val+t[x*2+1].val)%p;}int find(int x,int l,int r){if(t[x].l==l&&t[x].r==r)return t[x].val;downdata(x);int mid=(t[x].l+t[x].r)>>1;if(r<=mid) return find(x*2,l,r);else if(l>mid) return find(x*2+1,l,r);else return (find(x*2,l,mid)+find(x*2+1,mid+1,r))%p; }
}LT;//线段树
struct edge_node{int to,next;
}a[N*2];
void addl(int x,int y)
{a[++tot].to=y;a[tot].next=ls[x];ls[x]=tot;
}
void dfs1(int x,int fa)//第一遍预处理
{siz[x]=1;for(int i=ls[x];i;i=a[i].next){int y=a[i].to;if(y==fa) continue;dep[y]=dep[x]+1;f[y]=x;dfs1(y,x);siz[x]+=siz[y];if(siz[y]>siz[son[x]])son[x]=y;}
}
void dfs2(int x,int fa)//第二遍预处理
{seg[x]=++cnt;id[cnt]=x;if(son[x]){top[son[x]]=top[x];dfs2(son[x],x);}for(int i=ls[x];i;i=a[i].next){int y=a[i].to;if(y==fa||y==son[x]) continue;top[y]=y;dfs2(y,x);}
}
void path_change(int x,int y,int z)//路径修改
{while(top[x]!=top[y]){if(dep[top[x]]<dep[top[y]]) swap(x,y);//哪个深度大跳哪个LT.change(1,seg[top[x]],seg[x],z);//路径修改x=f[top[x]];//跳}if(dep[x]>dep[y]) swap(x,y);LT.change(1,seg[x],seg[y],z);return;
}
int path_ask(int x,int y)//路径查询—同上
{int ans=0;while(top[x]!=top[y]){if(dep[top[x]]<dep[top[y]]) swap(x,y);(ans+=LT.find(1,seg[top[x]],seg[x]))%=p;x=f[top[x]];}if(dep[x]>dep[y]) swap(x,y);(ans+=LT.find(1,seg[x],seg[y]))%=p;return ans;
}
int main()
{scanf("%d%d%d%d",&n,&m,&s,&p);for(int i=1;i<=n;i++)scanf("%d",&pw[i]);for(int i=1;i<n;i++){int x,y;scanf("%d%d",&x,&y);addl(x,y);addl(y,x);}dfs1(s,0);top[s]=s;dfs2(s,0);LT.build(1,1,n);for(int i=1;i<=m;i++){int t,x,y,z;scanf("%d%d",&t,&x);if(t==1){scanf("%d%d",&y,&z);path_change(x,y,z);}if(t==2){scanf("%d",&y);printf("%d\n",path_ask(x,y));}if(t==3){scanf("%d",&z);LT.change(1,seg[x],seg[x]+siz[x]-1,z);}if(t==4){printf("%d\n",LT.find(1,seg[x],seg[x]+siz[x]-1));}}
}
Part 5:练习题
luogu3870−[TJOI2009]luogu3870-[TJOI2009]luogu3870−[TJOI2009]开关【分块】
luogu4008−[NOI2003]luogu4008-[NOI2003]luogu4008−[NOI2003]文本编辑器【分块】
luogu2634−[luogu2634-[luogu2634−[国家集训队]]]聪聪可可【点分治】
luogu4178−Treeluogu4178-Treeluogu4178−Tree【点分治】
luogu3714−[BJOI2017]luogu3714-[BJOI2017]luogu3714−[BJOI2017]树的难题【点分治】
luogu3345−[ZJOI2015]luogu3345-[ZJOI2015]luogu3345−[ZJOI2015]幻想乡战略游戏【点分治】
luogu1503−luogu1503-luogu1503−鬼子进村【TreapTreapTreap】
luogu1533−luogu1533-luogu1533−可怜的狗狗【TreapTreapTreap】
luogu3313−[SDOI2014]luogu3313-[SDOI2014]luogu3313−[SDOI2014]旅行【主席树,,,树链剖分】
luogu2486−[SDOI2011]luogu2486-[SDOI2011]luogu2486−[SDOI2011]染色【树链剖分】
luogu2146−[NOI2015]luogu2146-[NOI2015]luogu2146−[NOI2015]软件包管理器【树链剖分】