西安day2之基础数据结构

数据结构研究组织数据的方式,即如何有效的储存、修改、查询数据。
研究数据结构的常用方法是转化和均衡。转化即将不熟悉的问题转化成熟悉的问题,例如将树转化成序列,将图转化为树;均衡即平衡预处理、修改、查询的复杂度,以达到更好的效率。
首先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
传送门

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值