数据结构学习笔记

· · 算法·理论

1.单调栈、队列、优先队列

(1)单调栈:单调递增或单调递减的栈。

它适用于找左边/右边第一个比自己大的元素(位置)。

优点:时间复杂度为O(n)。

模版题:

洛谷 5788

(2)单调队列:单调递减或单调递增的队列。

它能够动态地维护定长序列中的最值。

优点:可以降低时间复杂度。

模版题:

洛谷 P1886

(3)优先队列:在优先队列中,元素被赋予优先级。当访问元素

时,具有最高优先级的元素最先删除。优先队列具有最高级先出

(first in, largest out)的行为特征。通常采用堆数据结构来实现。

它适用于动态维持有序状态的场景。

模版题:

洛谷 P3378

2.树状数组

树状数组结合了树的思想,常用来处理前缀问题。

优点:修改和查询节点的复杂度都是O(logN)。

模版题:

洛谷 P3374 P3368

树状数组求逆序对

cin>>n;
for(int i=1;i<=n;i++)
{
    cin>>a[i];
    maxx=max(maxx, a[i]);
}
for(int i=1;i<=n;i++)
{   
    t1[i]=query(maxx)-query(a[i]);
    add(a[i], 1);
}

3.线段树

线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一

些单元区间,每个单元区间对应线段树中的一个叶结点。

普通线段树:

#include<bits/stdc++.h>
#define ll long long 
#define lid (id<<1)
#define rid (id<<1|1)
using namespace std;
ll n, m, a[500005], x, y, z;
string b;
struct seg_tree{
   ll l, r;
   ll sum, lazy;
}tr[2000005]; 
void pushup(int id)
{
    tr[id].sum=tr[lid].sum+tr[rid].sum;
}
void build(ll id, ll l, ll r) //建树 
{
    tr[id].l=l;
    tr[id].r=r;
    if(l==r)
    {
        tr[id].sum=a[l];
        return ; 
    }
    ll mid=(l+r)>>1;
    build(lid, l, mid);
    build(rid, mid+1, r);
    pushup(id);
}
void update(int id,int l,int k)//单点修改
{
    if(tr[id].l==l&&tr[id].r==l)
    {
        tr[id].sum+=k;
        return ;
    }
    int mid=(tr[id].r+tr[id].l)>>1;
    if(mid>=l)
    {
        update(lid,l,k);
    }
    else
    {
        update(rid,l,k);
    }
    pushup(id);
}
void pushdown(ll id) //下放lazy 
{
    if(tr[id].lazy&&tr[id].l!=tr[id].r)
    {
        tr[lid].lazy+=tr[id].lazy;
        tr[rid].lazy+=tr[id].lazy;
        tr[lid].sum+=tr[id].lazy*(tr[lid].r-tr[lid].l+1);
        tr[rid].sum+=tr[id].lazy*(tr[rid].r-tr[rid].l+1);
        tr[id].lazy=0;
   }
}
void modify(ll id, ll l, ll r, ll val)//区间修改 
{
    pushdown(id);
    if(tr[id].l==l&&tr[id].r==r)
    {
        tr[id].lazy+=val;
        tr[id].sum+=val*(tr[id].r-tr[id].l+1);
        return ;
    }   
    ll mid=(tr[id].l+tr[id].r)>>1;
    if(r<=mid)
    {
        modify(lid, l, r, val);
    }
    else if(l>mid)
    {
        modify(rid, l, r, val);
    }
    else
    {
        modify(lid, l, mid, val);
        modify(rid, mid+1, r, val);
    }
    pushup(id);
}
ll query(ll id,ll l, ll r) //区间查询 
{
    pushdown(id);
    if(tr[id].l==l&&tr[id].r==r)
    {
        return tr[id].sum;
    }
    int mid=(tr[id].l+tr[id].r)>>1;
    if(r<=mid)
    {
        return query(lid, l, r);
    }
    if(l>mid)
    {
        return query(rid, l, r);
    }
    return query(lid,l,mid)+query(rid,mid+1,r);
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];      
    }
    build(1,1,n);
    cin>>m;
    for(int i=1;i<=m;i++)
    {   
        cin>>b;
        if(b=="ADD")
        {
            cin>>x>>y>>z; 
            modify(1, x, y, z);
        }
        else
        {
            cin>>x>>y;
            cout<<query(1,x,y)<<endl;
        }
   }
   return 0;
}

维护最大子段和(应该是对的)

#include<bits/stdc++.h>
#define lid id<<1
#define rid id<<1|1
using namespace std;
const int maxn=1e5+10;
int n, m, a[maxn];
struct seg_tree{
    int ms, ls, rs, s;//最大子段和,区间紧靠左端点的最大子段和,区间紧靠左端点的最大子段和,区间子段和  
}tr[maxn<<2];
void pushup(int id)
{
    tr[id].ms=max(max(tr[lid].ms, tr[rid].ms), tr[lid].rs+tr[rid].ls);
    tr[id].ls=max(tr[lid].ls,tr[rid].ls+tr[lid].s);
    tr[id].rs=max(tr[rid].rs,tr[lid].rs+tr[rid].s);
    tr[id].s=tr[lid].s+tr[rid].s;
}
void build(int id,int l,int r)
{
    if(l==r)
    {
        tr[id].ms=tr[id].ls=tr[id].rs=tr[id].s=a[l];
        return ;
    }
    int mid=(l+r)>>1;
    build(lid, l, mid);
    build(rid, mid+1, r);
    pushup(id);
}
void add(int id,int l,int r,int u,int v)
{
    if(l==r)
    {
        tr[id].ms=tr[id].ls=tr[id].rs=tr[id].s=v;
        return ;
    }
    int mid=(l+r)>>1;
    if(u<=mid)
    {
        add(lid, l, mid, u, v);
    }
    else
    {
        add(rid, mid+1, r, u, v);
    }
    pushup(id);
}
seg_tree query(int id,int l,int r,int ql,int qr)
{
    if(ql<=l&&r<=qr)
    {
        return tr[id];
    }
    seg_tree x, y, w;
    int mid=(l+r)>>1;
    if(qr<=mid)
    {
        w=query(lid, l, mid, ql, qr);
    }
    else if(ql>mid) 
    {
        w=query(rid, mid+1, r, ql, qr);
    }
    else
    {
        x=query(lid, l, mid, ql, mid);
        y=query(rid, mid+1, r, mid+1, qr);
        w.s=x.s+y.s;
        w.ls=max(x.ls, x.s+y.ls);
        w.rs=max(y.rs, y.s+x.rs);
        w.ms=max(max(x.ms, y.ms), x.rs+y.ls);
    }
    return w;
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
    }
    cin>>m;
    build(1, 1, n);
    for(int i=1;i<=m;i++)
    {
        int x, y, z;
        cin>>x>>y>>z;
        if(x==0) add(1, 1, n, y, z);
        else 
        {
            seg_tree x=query(1, 1, n, y, z);
            cout<<x.ms<<endl;
        }
    }
    return 0;
} 

线段树做题技巧:

一般需要线段树优化的题有一些特点:

  1. 有明显的修改和查询操作,考虑该如何转换。
  2. 有类似于合并的操作,一段区间的值与两个端点的合并有关。
  3. 直接考虑计算时间复杂度。

线段树优化怎么写:

  1. 线段树要维护的就是与答案相关的值(一般都是能协助区间合并的),就像dp状态设计一样,例如山海经中的最大子段和、区间前后缀等等。
  2. 合并可能会很复杂,考虑分类讨论。
  3. 剩下的直接套板子。

动态开点线段树(千万不要复制,是错的)

int cnt=1, ans;
void pushdown(int id,int l,int r)
{
    if(lazy[id])
    {
        if(!ls[id]) ls[id]=++cnt;
        if(!rs[id]) rs[id]=++cnt;
        int mid=(l+r)>>1;
        lazy[ls[id]]+=lazy[id];
        lazy[rs[id]]+=lazy[id];
        tr[ls[id]]+=lazy[id]*(mid-l+1);
        tr[rs[id]]+=lazy[id]*(r-mid);
        lazy[id]=0;
    }
}
void pushup(int id)
{
    tr[id]=tr[ls[id]]+tr[rs[id]];
}
void add(int &id,int l,int r,int x,int v)//单点修改 
{
    if(!id) id=++cnt;
    if(l==r)
    {
        tr[id]=v;
        return ;
    }
    int mid=(l+r)>>1;
    if(x<=mid)
    {
        return add(id, l, mid, x, v);
    }
    else
    {
        return add(id, mid+1, r, x, v);
    }
    pushup(id);
}
void updata(int &id,int l,int r,int vl,int vr,int v)//区间修改 
{
    if(!id) id=++cnt;
    if(r<vl||l>vr)  return ;
    if(vl<=l&&r<=vr)
    {
        tr[id]+=(r-l+1)*v;
        return ;
    }
    int mid=(l+r)/2;
    pushdown(id, l, r);
    updata(ls[id], l, mid, vl, vr, v);
    updata(rs[id], mid+1, r, vl, vr, v);
} 
int query(int id,int l,int r,int x)//区间查询 
{
    if(!id) return 0;
    pushdown(id, l, r);
    if(r<vl||l>vr) return 0;
    if(vl<=l&&r<=vr)
    {
        return tr[id];
    }
    return query(ls[id], l, mid, vl, vr)+query(ls[id], mid+1, r, vl, vr)
}
int query(int id,int vl,int vr,int l,int r)
{
    if(!id) return 0;
    if(vl<=l&&r<=vr)
    {
        return si[id];
    }
    int mid=(l+r)>>1;
    pushdown(id);
    int sum=0;
    if(vl<=mid) 
    {
        sum+=query(ls[id], vl, vr, l, mid);
    }
    if(vr>mid)
    {
        sum+=query(rs[id], vl, vr, mid+1, r);
    }
    return sum;
}
int find(int id,int l,int r,int k)
{
    if(l==r)    return l;
    pushdown(id);
    int mid=(l+r)>>1;
    if(k<=si[ls[id]])
    {
        return find(ls[id], l, mid, k);
    } 
    else return find(rs[id], mid+1, r, k-si[ls[id]]);
}

线段树合并

int merge(int a,int b,int x,int y)
{
    if(!a) return b;
    if(!b) return a;
    if(x==y)
    {
        tr[a]+=tr[b];
        return a;
    }
    int mid=(x+y)>>1;
    ls[a]=merge(ls[a], ls[b], x, mid);
    rs[a]=merge(rs[a], rs[b], mid+1, y);
//  pushup(a);
    return a;
}

还有这一版

int merge(int a, int b, int l, int r) 
{
    if(!a||!b) 
    {
        return a+b;
    }
    int p=++cnt;
    if(l==r) 
    {
        tr[p]=tr[a]+tr[b];
        return p;
    }
    int mid=(l+r)>>1;
    tr[p]=tr[a]+tr[b];
    ls[p]=merge(ls[a], ls[b], l, mid);
    rs[p]=merge(rs[a], rs[b], mid+1, r);
    return p;
}

4.树链剖分

树剖是通过轻重边将树分割成多条链,然后利用数据结构来维护这些链(本质上是一种优化暴力)。

一些概念和性质

重儿子:父亲节点的所有儿子中子树结点数目最多的节点。

轻儿子:父亲节点中除了重儿子以外的儿子

其他什么的也不用解释了。

板子:

struct edge{
    int next,int to;
}edge[maxn<<1];
struct node{
    int sum, lazy, l, r, lid, rid;
}node[maxn<<1];
int rt, n, m, r, a[maxn], cnt, d[maxn], f[maxn], head[maxn];
int size[maxn], son[maxn], rk[maxn], top[maxn], id[maxn];
//d:深度 f:父节点 size:子树节点个数 son:重儿子 
//rk:当前dfs标号在原树中所对应的节点编号 top:当前节点所在链的顶端节点
//id:每个节点剖分后的新编号 
void dfs1(int u,int fa,int dep)//处理出f,size,son,d数组 
{
    f[u]=fa;
    d[u]=dep;
    size[u]=1;
    for(int i=head[u];i;i=edge[i].next)
    {
        int v=edge[i].to;
        if(v==fa) continue;
        dfs1(v,u,dep+1);
        size[u]+=size[v];
        if(size[v]>size[son[u]])
        {
            son[u]=v;
        }
    }
}
void dfs2(int u,int t)//处理出top,id,rk数组 (t表示重链顶端) 
{
    top[u]=t;
    id[u]=++cnt;
    rk[cnt]=u;
    if(!son[u]) return ;
    dfs2(son[u],t);
    for(int i=head[u];i;i=edge[i].next)//轻链 
    {
        int v=edge[i].to;
        if(v!=son[u]&&v!=f[u])
        {
            dfs2(v,v);
        }
    }
}

查找

int query(int id,int l,int r)
{
    if(l<=tr[id].l&&r>=tr[id].r)
    {
        return tr[id].sum;
    }
    int mid=(tr[id].l+tr[id].r)/2;
    int res=0;
    if(l<=mid) 
    {
        res+=query(lc, l, r);
    }
    if(r>mid)
    {
        res+=query(rc, l, r);
    } 
    return res;
}

边权放点权

#include<bits/stdc++.h>
#define lid (u*2)
#define rid (u*2+1)
using namespace std;
const int maxn=1e5+10;
int n,cnt, x[maxn], y[maxn], z[maxn];
int dian[maxn];
int a[maxn];
int son[maxn],f[maxn],size[maxn],top[maxn],d[maxn],rk[maxn],id[maxn];
int tr[maxn<<2];
struct edge{
    int next,to,w;
}edge[maxn<<1];
int head[maxn], edgenum;
void add(int from,int to,int w)
{
    edge[++edgenum].next=head[from];
    edge[edgenum].to=to;
    edge[edgenum].w=w;
    head[from]=edgenum;
}
void dfs1(int u,int fa)
{
    f[u]=fa;
    d[u]=d[fa]+1;
    size[u]=1;
    for(int i=head[u];i;i=edge[i].next)
    {
        int y=edge[i].to;
        if(y==fa) continue;
        a[y]=edge[i].w;
        dfs1(y,u);
        size[u]+=size[y];
        if(size[y]>size[son[u]])
        {
            son[u]=y;
        }
    }
}
void dfs2(int u,int t)
{
    top[u]=t;
    id[u]=++cnt;
    rk[cnt]=u;
    if(son[u]) 
    {
        dfs2(son[u],t);
    }
    for(int i=head[u];i;i=edge[i].next)
    {
        int y=edge[i].to;
        if(y!=son[u]&&y!=f[u])
        {
            dfs2(y,y);
        }
    }
}
void build(int u,int l,int r)
{
    if(l==r)
    {
        tr[u]=a[rk[l]];
        return;
    }
    int mid=(l+r)>>1;
    build(lid,l,mid);
    build(rid,mid+1,r);
    tr[u]=max(tr[lid],tr[rid]);
    return;
}
void updata(int u,int l,int r,int pos,int z)
{
    if(l==r)
    {
        tr[u]=z;
        return;
    }
    int mid=(l+r)>>1;
    if(pos<=mid)
    {
        updata(lid,l,mid,pos,z);
    }
    else 
    {
        updata(rid,mid+1,r,pos,z);
    }
    tr[u]=max(tr[lid],tr[rid]);
}
int mx(int u,int l,int r,int ql,int qr)
{
    if(ql>r||qr<l)
    {
        return -1e9;
    }
    if(ql<=l&&r<=qr)
    {
        return tr[u];
    }
    int mid=(l+r)>>1;
    return max(mx(lid,l,mid,ql,qr),mx(rid,mid+1,r,ql,qr));
}
int qsum(int x,int y)
{
    int res=-1e9;
    while(top[x]!=top[y])
    {
        if(d[top[x]]<d[top[y]])
        {
            swap(x,y);
        }
        res=max(res,mx(1,1,n,id[top[x]],id[x]));
        x=f[top[x]];
    }
    if(d[x]>d[y])  swap(x,y);
    res=max(res,mx(1,1,n,id[x]+1,id[y]));
    return res;
}
int main()
{
    cin>>n;
    for(int i=1;i<n;i++)
    {
        cin>>x[i]>>y[i]>>z[i];
        add(x[i], y[i], z[i]);
        add(y[i], x[i], z[i]);
    }
    dfs1(1,0);
    dfs2(1,1);
    for(int i=1;i<n;i++)
    {
        if(d[x[i]]>d[y[i]])
        {
            swap(x[i], y[i]);
        }
    }
    build(1,1,n);
    string s;
    while(cin>>s)
    {
        if(s[0]=='D') break;
        if(s[0]=='C')
        {
            int x,z;
            cin>>x>>z;
            updata(1,1,n,id[y[x]],z);
        }
        if(s[0]=='Q')
        {
            int x,y;
            cin>>x>>y;
            cout<<qsum(x, y)<<endl;
        }
    }
    return 0;
}

有时间再来好好修整一下 