数据结构学习笔记

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

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

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

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

模版题:洛谷 5788

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

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

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

模版题:洛谷 P1886

3)优先队列:在优先队列中,元素被赋予优先级。当访问元素时,具有最高优先级的元素最先删除。优先队列具有最高级先出(first in, largest out)的行为特征。通常采用堆数据结构来实现。

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

模版题:洛谷 P3378

2.树状数组

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

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

模版题:洛谷 P3374 P3368

一维:

点击查看代码
#define lowbit(x) (x&(-x))
int sum[maxn];
void add(int pos,int val)
{
while(pos<=n)
{
sum[pos]+=val;
pos+=lowbit(pos);
}
}
int query(int pos)
{
int res=0;
while(pos>0)
{
res+=sum[pos];
pos-=lowbit(pos);
}
return res;
}
二维:
点击查看代码
long long lowbit(int x)
{
return x&(-x);
}
void add(int x,int y,int k)
{
for(int i=x;i<=n;i+=lowbit(i))
{
for(int j=y;j<=m;j+=lowbit(j))
{
s[i][j]+=k;
}
}
}
long long getsum(int x,int y)
{
long long ans=0;
for(int i=x;i;i-=lowbit(i))
{
for(int j=y;j;j-=lowbit(j))
{
ans+=s[i][j];
}
}
return ans;
}
void add(int x,int y,int k)
{
for(int i=x;i<=n;i+=lowbit(i))
{
for(int j=y;j<=m;j+=lowbit(j))
{
c1[i][j]+=k;
c2[i][j]+=x*k;
c3[i][j]+=y*k;
c4[i][j]+=x*y*k;
}
}
}
int getsum(int x,int y)
{
int ans=0;
for(int i=x;i;i-=lowbit(i))
{
for(int j=y;j;j-=lowbit(j))
{
ans+=(x+1)*(y+1)*c1[i][j]-(y+1)*c2[i][j]-(x+1)*c3[i][j]+c4[i][j];
}
}
return ans;
}

树状数组求逆序对

点击查看代码
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 int long long
#define lid (id<<1)
#define rid (id<<1|1)
using namespace std;
int n, m, a[500005], x, y, z;
string b;
struct seg_tree{
int l, r;
int sum, lazy;
}tr[2000005];
void pushup(int id)
{
tr[id].sum=tr[lid].sum+tr[rid].sum;
}
void build(int id, int l, int r) //建树
{
tr[id].l=l;
tr[id].r=r;
if(l==r)
{
tr[id].sum=a[l];
return ;
}
int 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==tr[id].r)
{
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(int 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(int id, int l, int r, int 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 ;
}
int 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);
}
int query(int id,int l, int 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);
}
signed 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. 直接考虑计算时间复杂度。
  4. 遇到要求一些满足特殊要求的子序列、子串长度,考虑维护最大子段和版线段树。

线段树怎么写:

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

请原谅我马蜂突变(已修改),因为实在找不到固定的板子(
我已经尽力在补救了。

动态开点线段树

点击查看代码
struct seg_tree{
int l, r, sum, maxx, lazy;
}tr[maxn<<4];
void pushup(int id)
{
tr[id].maxx=max(tr[tr[id].l].maxx, tr[tr[id].r].maxx);
tr[id].sum=tr[tr[id].l].sum+tr[tr[id].r].sum;
}
void pushdown(int id,int l,int r)
{
if(tr[id].lazy)
{
if(!tr[id].l) tr[id].l=++cnt;
if(!tr[id].r) tr[id].r=++cnt;
int mid=(l+r)>>1;
tr[tr[id].l].lazy+=tr[id].lazy;
tr[tr[id].r].lazy+=tr[id].lazy;
tr[tr[id].l].sum+=tr[id].lazy*(mid-l+1);
tr[tr[id].r].sum+=tr[id].lazy*(r-mid);
tr[id].lazy=0;
}
}
void update(int &id,int l,int r,int p,int val)//单点修改
{
if(!id) id=++cnt;
if(l==r)
{
tr[id].sum=tr[id].maxx=val;
return ;
}
int mid=(l+r)>>1;
if(p<=mid) update(tr[id].l, l, mid, p, val);
else update(tr[id].r, mid+1, r, p, val);
pushup(id);
}
void updata(int &id,int l,int r,int ll,int rr,int val)//区间修改
{
if(!id) id=++cnt;
if(r<ll||l>rr) return ;
if(ll<=l&&r<=rr)
{
tr[id].sum+=(r-l+1)*val;
return ;
}
int mid=(l+r)>>1;
pushdown(id, l, r);
updata(tr[id].l, l, mid, ll, rr, val);
updata(tr[id].r, mid+1, r, ll, rr, val);
}
int query(int id,int l,int r,int ll,int rr)
{
if(!id) return 0;
if(r<ll||l>rr) return 0;
if(l>=ll&&r<=rr) return tr[id].sum;
int mid=(l+r)>>1;
if(rr<=mid) return query(tr[id].l, l, mid, ll, rr);
else if(ll>mid) return query(tr[id].r, mid+1, r, ll, rr);
else return query(tr[id].l, l, mid, ll, mid)+query(tr[id].r, mid+1, r, mid+1, rr);
}

线段树合并与分裂

下面的操作大都是在权值线段树(就是一个桶)上进行的。

前置知识:一颗权值线段树的叶子节点维护的是“有几个1”、“有几个2”。。。
支持操作:

  • 添加一个元素
  • 查找一个元素出现次数
  • 查找区间的元素个数
  • 查询所有元素的第k大/小的元素

线段树合并:

就是将树的信息相加,原理很简单,就是将对应位置的点相加即可。

点击查看代码
int merge(int a, int b,int l,int r)
{
if(!a||!b)
{
return a+b;
}
if(l==r)
{
tr[a].num+=tr[b].num;
del(b);
return a;
}
int mid=(l+r)>>1;
tr[a].l=merge(tr[a].l, tr[b].l, l, mid);
tr[a].r=merge(tr[a].r, tr[b].r, mid+1, r);
pushup(a);
del(b);
return a;
}

还有这一版

点击查看代码
//未修整
int merge(int a, int b)
{
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;
}

线段树分裂:

是将以 a 为根的线段树中保留排名为 1 到 k 中的数而把其他值给以 b 为根的线段树中。

点击查看代码
void split(int a,int &b,int k)
{
if(!a) return ;
b=newnode();
int v=tr[tr[a].l].num;
if(k>v) split(tr[a].r, tr[b].r, k-v);
else swap(tr[a].r, tr[b].r);
if(k<v) split(tr[a].l, tr[b].l, k);
tr[b].num=tr[a].num-k;
tr[a].num=k;
}

4.树链剖分

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


一些概念和性质

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

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

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

  • 整棵树会被剖分成若干条重链。
  • 轻儿子一定是每条重链的顶点。
  • 任意一条路径被切分成不超过 logn 条链。
  • 重链的各个节点的dfs序都是连续的。

板子:

点击查看代码
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], dfn[maxn];
//d:深度 f:父节点 size:子树节点个数 son:重儿子
//rk:当前dfs标号在原树中所对应的节点编号 top:当前节点所在链的顶端节点
//dfn:每个节点剖分后的新编号
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,dfn,rk数组 (t表示重链顶端)
{
top[u]=t;
dfn[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 qsum(int u,int v)
{
int res=0;
while(top[u]!=top[v])
{
if(dep[top[u]]<dep[top[v]]) swap(u, v);
res+=query(rt, 1, n, dfn[top[u]], dfn[u]);
u=f[top[u]];
}
if(dep[u]<dep[v]) swap(u, v);
res+=query(rt, 1, n, dfn[v], dfn[u]);
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],dfn[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;
dfn[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,dfn[top[x]],dfn[x]));
x=f[top[x]];
}
if(d[x]>d[y]) swap(x,y);
res=max(res,mx(1,1,n,dfn[x]+1,dfn[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,dfn[y[x]],z);
}
if(s[0]=='Q')
{
int x,y;
cin>>x>>y;
cout<<qsum(x, y)<<endl;
}
}
return 0;
}

有时间再来好好修整一下

posted @   zhouyiran2011  阅读(3)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示