数据结构研究组织数据的方式,即如何有效的储存、修改、查询数据。
研究数据结构的常用方法是转化和均衡。转化即将不熟悉的问题转化成熟悉的问题,例如将树转化成序列,将图转化为树;均衡即平衡预处理、修改、查询的复杂度,以达到更好的效率。
首先ST表
不讲
因为我不会所有的St表问题都可以用树状数组和线段树解决
单调队列单调栈
上题
给一个序列ai,对于每个位置,请你指出最小的j>i,使得aj>ai,或者声明无解。
n≤5e6
正解单调栈。从左向右扫,维护一个单调不减的栈。每个元素加入之前,将栈顶小于它的元素的答案记录成当前位置并出栈。
给定一个序列ai和一个数m,对于所有的m≤i≤n,求[m−i+1, i]这段区间中元素的最大值。
n≤5e6
注意到所有询问区间的长度都是定值。考虑这样的算法:从左向右扫过去,用一个数据结构维护最大值。
如果两个元素i<j有ai≤aj,那么i在j之后就没有贡献了。
说白了就是
“如果xxx比你晓小,还比你强,你就没有机会了”
要用的数据结构就是单调队列。
这个单调队列中元素的值是单调增的,而元素下标是单调减的。换句话说,这个队列的右边是“更强的”,左边是“更晓小的”。
插入一个元素的时候,在左边删掉那些没机会的元素,再插入进去;从右边删掉那些已经跑出区间的元素。
那么每个位置右边的值就是答案。
树状数组
树状数组被设计为一种支持单点修改、快速计算前缀和的数据结构。
特别的,当询问具有可减性的时候,可以用于维护区间询问。
首先是单点修改单点询问
利用二进制拆分的思想
如
1=20
2=21
3=20+21
4=22
……
(我不会告诉你们^不是异或)
怎么拆的?
有一句话说的好
世界上只有10种人,一种是懂2进制的,一种是不懂2进制的——某
臭不要脸的axr
1=1
2=10
3=11
4=100
……
还是那个问题
怎么拆的?
以26为例
26=24+23+ 21
26=11010
取反
~26=00101
加一
~26+1=00110
按位与&
不会?右转是天台
就会得到10,即21
26-21=24=11000
~24+1=01000
24&(~24+1)=23
24-23=16=10000
16&(~16+1)=24
16-24=0
明白了吗?
就是x&(~x+1)
即x&(-x)
如何实现?
inline int lowbit(int x)
{
return x&(-x);
}
inline void update(int x,int y)//单点修改a[x],使其+y
{
for(;x<=n;x+=lowbit(x))a[x]+=y;
}
inline int sum(int x)//求前缀和
{
int ans=0;
for(;x;x-=lowbit(x))ans+=a[x];
return ans;
}
区间修改和区间查询呢
这里有一个很重要的思想叫差分
如下
给定一个序列a[1…n],m次操作,两种操作形式
1 l r x:区间[l,r]中每一个元素加上x
2 l r:求区间[l,r]和
n, m≤1e6
线段树?亲测喜提re
考虑维护差分序列,这样可以做到O(1)修改,最后O(n)处理。
差分和前缀和是最简单、最基础的套路之一。
具体实现
设原数组为a,差分序列为b
b[1]=a[1],b[2]=a[2]-a[1],b[3]=a[3]-a[2]…以此类推
则差分序列前缀和则为a[n]。
前缀和怎么维护?
如上
如何修改[l,r]?
b[l]+=w,b[r+1]-=w;
正确性显然成立
实现?上代码
inline void update(ll x,ll y)
{
for(register int i=x;i<=n;i+=lowbit(i))
{
b[i]+=y;c[i]+=y*(x-1);
}
}
不少人要问了
c数组tmd是啥?
a数组呢?
我来解释一下
首先读入的时候直接存b数组省内存,a数组就是b数组
之后思考如何修改?
先思考如何求和
以求区间[1,n]和为例
sum=a[1]+a[2]+…+a[n]
=b[1]+b[2]+b[1]+…+b[n…1]
=nb[1]+(n-1)b[2]+(n-2)b[3]+…+b[1]
=n(b[1…n])-0b[1]-1b[2]-…-(n-1)b[n]
=n(b[1…n])-sigma i=1 n (i-1)b[i]
设c[i]=(i-1)b[i]
则显然只需维护c数组与b数组
于是每次操作就要更新b数组与c数组
求和同理
完整代码如下
#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define db double
#define N 1000100
#define ls k<<1
#define rs k<<1|1
#define jd (c>='0'&&c<='9')
#define gg c=getchar()
inline ll read()
{
ll f=0,x=1;char gg;
for(;!jd;gg)if(c=='-')x=0;
for(;jd;gg)f=(f<<1)+(f<<3)+c-48;
return x?f:-f;
}
ll n,m,a[N],b,c[N];
inline ll lowbit(ll x)
{
return x&(-x);
}
inline void update(ll x,ll y)
{
for(register int i=x;i<=n;i+=lowbit(i))
{
a[i]+=y;c[i]+=y*(x-1);
}
}
inline ll sum(ll x)
{
ll ans=0;
for(register int i=x;i;i-=lowbit(i))ans+=a[i]*x-c[i];
return ans;
}
int main()
{
n=read(),m=read();
for(register int i=1;i<=n;i++)
{
ll k=read();
update(i,k-b);
b=k;
}
while(m--)
{
ll o=read(),p=read(),q=read();
if(o==1)
{
ll v=read();
update(p,v);
update(q+1,-v);
}
else
{
ll sa=sum(p-1);
ll sb=sum(q);
printf("%lld\n",sb-sa);
}
}
return 0;
}
没错,思想还是差分
线段树
线段树支持区间修改与区间查询
支持乘法!!!
加法神马的见树状数组
乘法废话少说背的板子直接上代码
#include<bits/stdc++.h>
using namespace std;
#define N 100100
#define db double
#define ll long long
#define ls k<<1
#define rs k<<1|1
#define gg c=getchar()
#define jd (c>='0'&&c<='9')
inline ll read()
{
ll f=0,x=1;char gg;
for(;!jd;gg)if(c=='-')x=0;
for(;jd;gg)f=(f<<1)+(f<<3)+c-48;
return x?f:-f;
}
struct tre
{
ll lz,sum,le,ri,lzm;
}t[N<<2];
ll a[N],n,mod,m;
void mood(ll k)
{
t[k].lz%=mod,t[k].lzm%=mod,t[k].sum%=mod;
}
void pushup(ll k)
{
t[k].sum=(t[ls].sum+t[rs].sum)%mod;
}
void build(ll k,ll l,ll r)
{
t[k].le=l,t[k].ri=r,t[k].lz=0,t[k].lzm=1;
if(l==r)
{
t[k].sum=a[l]%mod;
return;
}
ll mid=(l+r)>>1;
build(ls,l,mid);build(rs,mid+1,r);
pushup(k);
}
void pushdown(ll k)
{
if(!t[k].lz&&t[k].lzm==1)return;
t[ls].sum=t[ls].sum*t[k].lzm+t[k].lz*(t[ls].ri-t[ls].le+1);
t[rs].sum=t[rs].sum*t[k].lzm+t[k].lz*(t[rs].ri-t[rs].le+1);
t[ls].lzm*=t[k].lzm,t[rs].lzm*=t[k].lzm;
t[ls].lz=t[ls].lz*t[k].lzm+t[k].lz;
t[rs].lz=t[rs].lz*t[k].lzm+t[k].lz;
mood(ls);mood(rs);
t[k].lz=0,t[k].lzm=1;
}
void modify(ll k,ll l,ll r,ll v,ll m)
{
ll le=t[k].le,ri=t[k].ri;
if(l==le&&r==ri)
{
t[k].sum=t[k].sum*m+v*(ri-le+1);
t[k].lzm*=m;
t[k].lz=t[k].lz*m+v;
mood(k);
return;
}
pushdown(k);
ll mid=(le+ri)>>1;
if(l>mid)modify(rs,l,r,v,m);
else if(r<=mid)modify(ls,l,r,v,m);
else modify(ls,l,mid,v,m),modify(rs,mid+1,r,v,m);
pushup(k);
}
ll query(ll k,ll l,ll r)
{
ll le=t[k].le,ri=t[k].ri;
if(l==le&&r==ri)return t[k].sum%mod;
pushdown(k);
ll mid=(le+ri)>>1;
if(l>mid)return query(rs,l,r);
else if(r<=mid)return query(ls,l,r);
else return query(ls,l,mid)+query(rs,mid+1,r);
}
int main()
{
n=read();mod=read();
for(int i=1;i<=n;i++)a[i]=read();
build(1,1,n);m=read();
while(m--)
{
ll o=read(),p=read(),q=read();
if(o==3)printf("%lld\n",query(1,p,q)%mod);
else if(o==2)
{
ll v=read();
modify(1,p,q,v,1);
}
else
{
ll v=read();
modify(1,p,q,0,v);
}
}
return 0;
}
就讲到这吧太困了
ps:今日模拟巨水,水到我rank2
为什么不是rank1?
那个哥们跟我现学的lca
看题解请找神犇yhk:David_alwal
传送门