不包括字符串和图论内容。
某种意义上算是“简单”数据结构……
代码压行警告qwq
如果存在与数据结构有关的经典算法,也会予以列出。
1. 单调队列 (O(n))
单调队列和单调栈的讲解在这里
const int maxn=1000010;
int n,k,cnt,a[maxn],minans[maxn],maxans[maxn];
deque<int> minint,maxint;
void push(int pos)
{
while(!minint.empty()&&minint.front()<=pos-k)minint.pop_front();
while(!maxint.empty()&&maxint.front()<=pos-k)maxint.pop_front();
while(!minint.empty()&&a[minint.back()]>=a[pos])minint.pop_back();
while(!maxint.empty()&&a[maxint.back()]<=a[pos])maxint.pop_back();
minint.push_back(pos);maxint.push_back(pos);
}
//......
for(int i=1;i<k;i++)push(i);
for(int i=k;i<=n;i++)
{
push(i);cnt++;
minans[cnt]=a[minint.front()];
maxans[cnt]=a[maxint.front()];
}
2. 单调栈 (O(n))
const int maxn=3000010;
int n,a[maxn],ans[maxn];
stack<int> st;
for(int i=n;i>=1;i--)
{
while(!st.empty()&&a[st.top()]<=a[i])st.pop();
if(!st.empty())ans[i]=st.top();
st.push(i);
}
3. 并查集 (O(nalpha(n)))
路径压缩+启发式合并
玄学的时间复杂度分析
const int maxn=100010;
int n,fa[maxn],siz[maxn];
void init(int xx){for(int i=1;i<=xx;i++){fa[i]=i;siz[i]=1;}}
int find(int xx){return fa[xx]==xx?xx:fa[xx]=find(fa[xx]);}
int query(int xx,int yy){return find(xx)==find(yy);}
void merge(int xx,int yy)
{
int fx=find(xx),fy=find(yy);
if(fx==fy)return;
if(siz[fx]<siz[fy]){fa[fx]=fy;siz[fy]+=siz[fx];fa[xx]=fy;}
else{fa[fy]=fx;siz[fx]+=siz[fy];fa[yy]=fx;}
}
4. ST表 (O(nlog n)-O(1))
int n,m,ans,l,r,k,lg2[100010],st[100010][20];
for(int i=2;i<100010;i++)lg2[i]=lg2[i/2]+1;
for(int i=1;i<=n;i++)scanf("%d",&st[i][0]);
for(int j=1;j<=lg2[n];j++)
for(int i=1;i+(1<<(j-1))<=n;i++)
st[i][j]=max(st[i][j-1],st[i+(1<<(j-1))][j-1]);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&l,&r);k=lg2[r-l+1];
ans=max(st[l][k],st[r-(1<<k)+1][k]);
printf("%d
",ans);
}
5. 树状数组
5.1 树状数组模板 (O(nlog n)-O(log n))
预处理可以(O(n)),但没必要。
int n,m,a[500010];
inline int lowbit(int x){return x&(-x);}
inline void add(int x,int k){while(x<=n)a[x]+=k,x+=lowbit(x);}
inline int query(int pos)
{
int ans=0;
while(pos)ans+=a[pos],pos-=lowbit(pos);
return ans;
}
5.2 树状数组求逆序对 (O(nlog n))
建议遇到这种题直接归并排序(
struct node{int pos,x;}a[500010];
int rk[500010];
bool cmp(node xx,node yy)
{
if(xx.x!=yy.x)return xx.x<yy.x;
return xx.pos<yy.pos;
}
class Bittree
{
public:
int num=500010,datas[500010];
int lowbit(int x){return x&(-x);}
void add(int x,int k){while(x<=num)datas[x]+=k,x+=lowbit(x);}
int query(int pos)
{
int ans=0;
while(pos)ans+=datas[pos],pos-=lowbit(pos);
return ans;
}
}tree;
int n,ans;
//......
for(int i=1;i<=n;i++){scanf("%lld",&a[i].x);a[i].pos=i;}
sort(a+1,a+n+1,cmp);
for(int i=1;i<=n;i++)rk[a[i].pos]=i;
for(int i=1;i<=n;i++){tree.add(rk[i],1);ans+=i-tree.query(rk[i]);}
6. 线段树
6.1 复杂标记线段树 (O(n)-O(log n))
以luoguP3373为例。
高度模式化的代码。
const int maxn=100010;
int n,m,p,a[maxn];
struct node{int l,r,val,add,mul;}tree[maxn<<2];
void pushup(int x)
{
int lson=x<<1,rson=lson|1;
tree[x].val=(tree[lson].val+tree[rson].val)%p;
}
void pushmul(int x,int k)
{
tree[x].val*=k;tree[x].val%=p;
tree[x].mul*=k;tree[x].mul%=p;
tree[x].add*=k;tree[x].add%=p;
}
void pushadd(int x,int k)
{
tree[x].val+=k*(tree[x].r-tree[x].l+1);tree[x].val%=p;
tree[x].add+=k;tree[x].add%=p;
}
void pushdown(int x)
{
int lson=x<<1,rson=lson|1;
if(tree[x].l==tree[x].r)return;
if(tree[x].mul!=1)//if的先后是按标记的优先级排的
{
pushmul(lson,tree[x].mul);
pushmul(rson,tree[x].mul);
tree[x].mul=1;
}
if(tree[x].add!=0)
{
pushadd(lson,tree[x].add);
pushadd(rson,tree[x].add);
tree[x].add=0;
}
}
void build(int x,int l,int r)
{
tree[x]=(node){l,r,0,0,1};
if(l==r){tree[x].val=a[l];return;}
int mid=(tree[x].l+tree[x].r)>>1,lson=x<<1,rson=lson|1;
build(lson,l,mid);build(rson,mid+1,r);
pushup(x);
}
void modify_mul(int x,int l,int r,int k)
{
pushdown(x);
if(l<=tree[x].l&&r>=tree[x].r){pushmul(x,k);return;}
int mid=(tree[x].l+tree[x].r)>>1,lson=x<<1,rson=lson|1;
if(l<=mid)modify_mul(lson,l,r,k);
if(r>mid)modify_mul(rson,l,r,k);
pushup(x);
}
void modify_add(int x,int l,int r,int k)
{
pushdown(x);
if(l<=tree[x].l&&r>=tree[x].r){pushadd(x,k);return;}
int mid=(tree[x].l+tree[x].r)>>1,lson=x<<1,rson=lson|1;
if(l<=mid)modify_add(lson,l,r,k);
if(r>mid)modify_add(rson,l,r,k);
pushup(x);
}
int query_sum(int x,int l,int r)
{
pushdown(x);
if(l<=tree[x].l&&r>=tree[x].r)return tree[x].val;
int mid=(tree[x].l+tree[x].r)>>1,lson=x<<1,rson=lson|1,ans=0;
if(l<=mid){ans+=query_sum(lson,l,r);ans%=p;}
if(r>mid){ans+=query_sum(rson,l,r);ans%=p;}
return ans;
}
6.2 动态开点权值线段树 (O(nlog k))
luoguP3369
有些询问可以用map模拟,但这样写确实是原汁原味的权值线段树。
加强版卡空间过不去qaq
const int top=1e7+1;//根据值域进行相应改动
int n,cnt,rt;
struct node{int l,r,val;}tree[5000010];//看值域开,别MLE就行
void modify(int &k,int x,int l,int r,int add)
{
if(!k)k=++cnt;
tree[k].val+=add;
if(l==r)return;
int mid=(l+r)>>1;
if(x<=mid)modify(tree[k].l,x,l,mid,add);
else modify(tree[k].r,x,mid+1,r,add);
}
int query(int k,int treel,int treer,int l,int r)
{
if(!k)return 0;
if(l<=treel&&r>=treer)return tree[k].val;
int mid=(treel+treer)>>1,ans=0;
if(l<=mid)ans+=query(tree[k].l,treel,mid,l,r);
if(r>mid)ans+=query(tree[k].r,mid+1,treer,l,r);
return ans;
}
int getnum(int k,int x,int l,int r)
{
if(!k)return 0;
if(l==r)return l;
int mid=(l+r)>>1;
if(tree[tree[k].l].val>=x)return getnum(tree[k].l,x,l,mid);
else return getnum(tree[k].r,x-tree[tree[k].l].val,mid+1,r);
}
int getrank(int x){return query(rt,1,2e7+1,1,x-1)+1;}//这里的询问也是根据值域进行改动
int getpre(int x){return getnum(rt,getrank(x)-1,1,2e7+1);}
int getsuc(int x){return getnum(rt,getrank(x+1),1,2e7+1);}
6.3 扫描线
7. 树链剖分
7.1 轻重链剖分 (O(nlog^2 n))
const int maxn=100010;
int n,m,rt,p,cnt,dfncnt,a[maxn],w[maxn],h[maxn],dep[maxn],siz[maxn],son[maxn],fa[maxn],top[maxn],dfn[maxn];
struct node{int l,r,val,add;}tree[maxn<<2];
void pushup(int x)
{
int lson=x<<1,rson=lson|1;
tree[x].val=(tree[lson].val+tree[rson].val)%p;
}
void pushadd(int x,int k)
{
tree[x].val+=k%p*(tree[x].r-tree[x].l+1)%p;tree[x].val%=p;
tree[x].add+=k;tree[x].add%=p;
}
void pushdown(int x)
{
int lson=x<<1,rson=lson|1;
if(tree[x].l==tree[x].r)return;
if(tree[x].add!=0)
{
pushadd(lson,tree[x].add);
pushadd(rson,tree[x].add);
tree[x].add=0;
}
}
void build(int x,int l,int r)
{
tree[x]=(node){l,r,0,0};
if(l==r){tree[x].val=a[l]%p;return;}
int mid=(l+r)>>1,lson=x<<1,rson=lson|1;
build(lson,l,mid);build(rson,mid+1,r);
pushup(x);
}
void modify(int x,int l,int r,int k)
{
pushdown(x);
if(l<=tree[x].l&&r>=tree[x].r){pushadd(x,k);return;}
int mid=(tree[x].l+tree[x].r)>>1,lson=x<<1,rson=lson|1;
if(l<=mid)modify(lson,l,r,k);
if(r>mid)modify(rson,l,r,k);
pushup(x);
}
int query(int x,int l,int r)
{
pushdown(x);
if(l<=tree[x].l&&r>=tree[x].r)return tree[x].val%p;
int mid=(tree[x].l+tree[x].r)>>1,lson=x<<1,rson=lson|1,ans=0;
if(l<=mid){ans+=query(lson,l,r);ans%=p;}
if(r>mid){ans+=query(rson,l,r);ans%=p;}
return ans;
}
struct edge{int to,nxt;}e[maxn<<1];
void addedge(int u,int v){e[++cnt]=(edge){v,h[u]};h[u]=cnt;}
void add(int u,int v){addedge(u,v);addedge(v,u);}
void dfs1(int u,int f)
{
fa[u]=f;siz[u]++;dep[u]=dep[f]+1;
int maxsiz=0;
for(int i=h[u];i;i=e[i].nxt)
{
int p=e[i].to;
if(p!=f)
{
dfs1(p,u);siz[u]+=siz[p];
if(maxsiz<siz[p]){maxsiz=siz[p];son[u]=p;}
}
}
}
void dfs2(int u,int f)
{
top[u]=f;dfn[u]=++dfncnt;
if(w[u])modify(1,dfn[u],dfn[u],w[u]);
if(!son[u])return;
dfs2(son[u],f);
for(int i=h[u];i;i=e[i].nxt)
{
int p=e[i].to;
if(p!=son[u]&&p!=fa[u])dfs2(p,p);
}
}
int lca(int u,int v)//这个操作是模板题所没有的,但是轻重链剖分也资瓷,是O(nlogn)的
{
while(top[u]!=top[v])
{
if(dep[top[u]]<dep[top[v]])swap(u,v);
u=fa[top[u]];
}
if(dep[u]>dep[v])swap(u,v);
return u;
}
void addpath(int u,int v,int k)
{
while(top[u]!=top[v])
{
if(dep[top[u]]<dep[top[v]])swap(u,v);
modify(1,dfn[top[u]],dfn[u],k%p);
u=fa[top[u]];
}
if(dep[u]>dep[v])swap(u,v);
modify(1,dfn[u],dfn[v],k);
}
int querypath(int u,int v)
{
int ans=0;
while(top[u]!=top[v])
{
if(dep[top[u]]<dep[top[v]])swap(u,v);
ans+=query(1,dfn[top[u]],dfn[u]);ans%=p;
u=fa[top[u]];
}
if(dep[u]>dep[v])swap(u,v);
ans+=query(1,dfn[u],dfn[v]);ans%=p;
return ans;
}
void addtree(int u,int k){modify(1,dfn[u],dfn[u]+siz[u]-1,k%p);}
int querytree(int u){return query(1,dfn[u],dfn[u]+siz[u]-1);}
7.2 dsu on tree/静态链分治
7.3 长链剖分
8. 分块相关
9. 左偏树
luoguP3377
对于这道题,左偏树需要与并查集一起使用。
10. 平衡树
10.1 普通平衡树 (O(nlog n))
luoguP6136
快进到数据加强版QwQ
讲道理这个隐藏的数据范围是真的坑人……
10.1.1 fhq_treap版
const int maxn=2000010;
int ncnt,rt;
struct fhq_treap{int l,r,val,key,siz;}tree[maxn];
inline void update(int x){tree[x].siz=tree[tree[x].l].siz+tree[tree[x].r].siz+1;}
inline void build(int &x,int val)
{
x=++ncnt;
tree[x].val=val;
tree[x].key=rand()%10000000;
tree[x].siz=1;
}
void split(int x,int val,int &rx,int &ry)
{
if(!x){rx=ry=0;return;}
if(tree[x].val<=val){rx=x;split(tree[x].r,val,tree[x].r,ry);}
else{ry=x;split(tree[x].l,val,rx,tree[x].l);}
update(x);
}
int merge(int x,int y)
{
if(!x||!y)return x+y;
if(tree[x].key<tree[y].key)
{
tree[x].r=merge(tree[x].r,y);
update(x);return x;
}
else
{
tree[y].l=merge(x,tree[y].l);
update(y);return y;
}
}
void ins(int val)
{
int now,x,y;
split(rt,val,x,y);build(now,val);
rt=merge(merge(x,now),y);
}
void del(int val)
{
int x,y,z;
split(rt,val,x,z);split(x,val-1,x,y);
y=merge(tree[y].l,tree[y].r);
rt=merge(merge(x,y),z);
}
int getrank(int val)
{
int rk,x,y;split(rt,val-1,x,y);
rk=tree[x].siz+1;rt=merge(x,y);
return rk;
}
int getnum(int rk)
{
int now=rt;
while(now)
{
int lsiz=tree[tree[now].l].siz;
if(lsiz+1==rk)break;
else if(rk<=lsiz)now=tree[now].l;
else{rk-=lsiz+1;now=tree[now].r;}
}
return tree[now].val;
}
int getpre(int val){return getnum(getrank(val)-1);}
int getsuc(int val){return getnum(getrank(val+1));}
//...
srand(19260817);//玄学数字
10.1.2 splay版
const int maxn=2000010;
int ncnt,rt;
struct splay{int fa,ch[2],val,cnt,siz;}tree[maxn];
inline bool judge(int x,int f){return tree[f].ch[1]==x;}
inline void con(int x,int f,int s){tree[f].ch[s]=x;tree[x].fa=f;}
inline void update(int x){tree[x].siz=tree[tree[x].ch[0]].siz+tree[tree[x].ch[1]].siz+tree[x].cnt;}
inline void build(int &x,int f,int val)
{
x=++ncnt;
tree[x].fa=f;tree[x].val=val;
tree[x].siz=tree[x].cnt=1;
}
void rotate(int x)
{
int f=tree[x].fa,g=tree[f].fa,k=judge(x,f);
con(tree[x].ch[k^1],f,k);con(x,g,judge(f,g));con(f,x,k^1);
update(f);update(x);
}
void splaying(int x,int top)
{
if(!top)rt=x;
while(tree[x].fa!=top)
{
int f=tree[x].fa,g=tree[f].fa;
if(g!=top)judge(x,f)^judge(f,g)?rotate(x):rotate(f);
rotate(x);
}
}
void ins(int val,int &x,int fa)
{
if(!x){build(x,fa,val);splaying(x,0);}
else if(val<tree[x].val)ins(val,tree[x].ch[0],x);
else if(val>tree[x].val)ins(val,tree[x].ch[1],x);
else{tree[x].cnt++;splaying(x,0);}
}
void delnode(int x)
{
splaying(x,0);
if(tree[x].cnt>1)tree[x].cnt--;
else if(tree[x].ch[1])
{
int now=tree[x].ch[1];
while(tree[now].ch[0])now=tree[now].ch[0];
splaying(now,x);con(tree[x].ch[0],now,0);
rt=now;tree[now].fa=0;update(rt);
}
else{rt=tree[x].ch[0];tree[rt].fa=0;}
}
void del(int val,int x)
{
if(val==tree[x].val)delnode(x);
else if(val<tree[x].val)del(val,tree[x].ch[0]);
else del(val,tree[x].ch[1]);
}
int getrank(int val)
{
int now=rt,rk=1;
while(now)
{
int lsiz=tree[tree[now].ch[0]].siz;
if(val==tree[now].val){rk+=lsiz;splaying(now,0);break;}
else if(val<tree[now].val)now=tree[now].ch[0];
else
{
rk+=lsiz+tree[now].cnt;
now=tree[now].ch[1];
}
}
return rk;
}
int getnum(int rk)
{
int now=rt;
while(now)
{
int lsiz=tree[tree[now].ch[0]].siz;
if(lsiz+1<=rk&&rk<=lsiz+tree[now].cnt){splaying(now,0);break;}
else if(rk<=lsiz)now=tree[now].ch[0];
else
{
rk-=lsiz+tree[now].cnt;
now=tree[now].ch[1];
}
}
return tree[now].val;
}
int getpre(int val){return getnum(getrank(val)-1);}
int getsuc(int val){return getnum(getrank(val+1));}
10.2 文艺平衡树
10.2.1 fhq_treap版
10.2.2 splay版
11. 可持久化数据结构
11.1 可持久化数组 (O(nlog n))
const int maxn=1000010;
int n,cnt,rt[maxn],a[maxn];
struct node{int l,r,val;}tree[maxn*20];
void build(int &x,int l,int r)
{
x=++cnt;
if(l==r){tree[x].val=a[l];return;}
int mid=(l+r)>>1;
build(tree[x].l,l,mid);
build(tree[x].r,mid+1,r);
}
void modify(int &k,int ver,int x,int l,int r,int val)
{
k=++cnt;tree[k]=tree[ver];
if(l==r){tree[k].val=val;return;}
int mid=(l+r)>>1;
if(x<=mid)modify(tree[k].l,tree[ver].l,x,l,mid,val);
else modify(tree[k].r,tree[ver].r,x,mid+1,r,val);
}
int query(int k,int x,int l,int r)
{
if(l==r)return tree[k].val;
int mid=(l+r)>>1;
if(x<=mid)return query(tree[k].l,x,l,mid);
else return query(tree[k].r,x,mid+1,r);
}
11.2 主席树
11.3 可持久化并查集 (O(nlog^2 n))
不路径压缩+按秩合并的并查集。
就是个可持久化数组的简单应用。
时间复杂度口胡:
考虑将节点按照完全二叉树的方式进行合并。
则此时因为只有按秩合并,树高显然为(log n)级别的。
然后不停地query高度最小的节点,于是find函数也需要(log n)次递归,而可持久化数组的单次查询操作是(O(log n))的。
则find一次的时间复杂度为(O(log^2 n)),总的时间复杂度为(O(nlog^2 n))。
const int maxn=100010;
int n;
class ver_vector
{
public:
int cnt,rt[maxn],a[maxn];
struct node{int l,r,val;}tree[maxn*20];
void build(int &x,int l,int r)
{
x=++cnt;
if(l==r){tree[x].val=a[l];return;}
int mid=(l+r)>>1;
build(tree[x].l,l,mid);
build(tree[x].r,mid+1,r);
}
void modify(int &k,int ver,int x,int l,int r,int val)
{
k=++cnt;tree[k]=tree[ver];
if(l==r){tree[k].val=val;return;}
int mid=(l+r)>>1;
if(x<=mid)modify(tree[k].l,tree[ver].l,x,l,mid,val);
else modify(tree[k].r,tree[ver].r,x,mid+1,r,val);
}
int query(int k,int x,int l,int r)
{
if(l==r)return tree[k].val;
int mid=(l+r)>>1;
if(x<=mid)return query(tree[k].l,x,l,mid);
else return query(tree[k].r,x,mid+1,r);
}
}fa,height;
int find(int x,int ver)
{
int f=fa.query(fa.rt[ver],x,1,n);
if(f==x)return f;
else return find(f,ver);
}
void merge(int ver,int x,int y)
{
int fx=find(x,ver-1),fy=find(y,ver-1);
if(fx==fy)
{
fa.rt[ver]=fa.rt[ver-1];
height.rt[ver]=height.rt[ver-1];
return;
}
int hx=height.query(height.rt[ver-1],fx,1,n),hy=height.query(height.rt[ver-1],fy,1,n);
if(hx<hy)
{
fa.modify(fa.rt[ver],fa.rt[ver-1],fx,1,n,fy);
height.rt[ver]=height.rt[ver-1];
}
else if(hx>hy)
{
fa.modify(fa.rt[ver],fa.rt[ver-1],fy,1,n,fx);
height.rt[ver]=height.rt[ver-1];
}
else
{
fa.modify(fa.rt[ver],fa.rt[ver-1],fx,1,n,fy);
height.modify(height.rt[ver],height.rt[ver-1],fy,1,n,hy+1);
}
}