数据结构

树状数组

https://oi-wiki.org/ds/fenwick/

 

 

 

管辖区间

右边界:c数组下标i;

左边界:i - lowbit (i)+1;

      lowbit(i)表示c[i]区间长度 

所以c[i]管辖的区间为 [ i-lowbit(i)+1,i ];

int lowbit(int x){
    // x 的二进制中,最低位的 1 以及后面所有 0 组成的数。
    // lowbit(0b01011000) == 0b00001000
    //          ~~~~^~~~
    // lowbit(0b01110010) == 0b00000010
    //          ~~~~~~^~
    return x&-x;
}

区间查询

时间复杂度为O( log(n))

查找区间a[l,r]转化为查询a[1,l-1]和a[1,r]的和,求差。

查询a[1...x]的过程:

· 从c[x]开始往前跳,c[x]管辖有a[x-lowbit(x)+1,x];

· 令x→x - lowbit(x) ,如果x等于0说明走到了尽头,停止循环;否则回到第一步;

· 将跳到的c合并(可边跳边合并)

int getsum(int x){//a[1]..a[x]的和
    int ans=0;
    while(x>0){
        ans+=c[x];
        x-=lowbit(x);
    }
    return ans;
}

 

树状数组与其他树形态的性质

  • 性质一:对于x<=y,要么有c[x]与c[y]相交,要么不相交。
  • 性质二:c[x]真包含于c [ x+lowbit(x) ] 。
  • 性质三:对于任意x<y<x+lowbit(x),c[x]与c[y]不相交

事实上,树状数组的树形态是x向x+lowbit(x)连边得到的图,其中x+lowbit(x)是x的父亲

树满足的性质(设fa[u]表示u的直系父亲):

  • u<fa[u]
  • u大于u的任何一个后代,小于任何一个祖先
  • 点u的lowbit严格小于fa[u]的lowbit
  • 我们认为c[1]的高度为0,则点x的高度为log2lowbit(x),即二进制最低位1的位数
  • c[u]真包含于c[fa[u]]
  • c[u]真包含于c[v],v是u的任意一个祖先
  • c[u]真包含c[v],v是u的任意一个后代
  • 任意u>v,若u不是v的祖先,则c[u]与c[v]不相交
  • 任意u>v,若v不是u的后代,则c[u]与c[v]不相交
  • 设u=s*2k+1+2k,则其儿子的数量为log2lowbit(u),编号分别为u-2t(0<= t <k)
  • u的所有儿子对应c的管辖区间恰好拼接成[u-lowbit(u)+1,u-1]

单点修改

时间复杂度为O(log(n))

只需要修改包含了a[x]的管辖区间

管辖a[x]的c[y]一定包含c[x],所有y在树状数组树形态上是x的祖先,因此我们从c[x]开始找父亲。

设n表示a的大小,单点修改a[x]的过程:

·  初始令x'=x

·  修改c[x']

·  令x'→x'+lowbit(x'),如果x'>n说明已经跳到了尽头了,停止循环;否则回到第二步

 

以单点加给出实现:

void add(int x,int k){
    while(x<=n){
        c[x]+=k;
        x+=lowbit(x);
    }
}

 

区间加区间和

知识点:前缀和&差分

该问题使用两个树状数组维护差分数组解决

其中,序列a的差分数组为d,有ai=Σij=1dj

 

 

原数组a区间加维护方式:

对于维护差分数组di的树状数组,对l单点加v,r+1单点加-v;

对于维护di*i的树状数组,对l单点加v*l,r+1单点加-v*(r+1);

int t1[MAXN],t2[MAXN],n;
inline int lowbit(int x){
    return x&-x;
}
void add(int k,int v){
    int v1=k*v;
    while(k<=n){
        t1[k]+=v,t2[k]+=v1;
        //注意不能写成t2[k]+=k*v,因为k的值已经不是原数组的下标了
        k+=lowbit(k);
    }
}
int getsum(int *t,int k){
    int res=0;
    while(k){
        res+=t[k];
        k-=lowbit(k);
    }
    return res;
}
void add1(int l,int r,int v){
    add(l,v),add(r+1,-v);
}
long long getsum1(int l,int r){
    return (r+1ll)*getsum(t1,r)-1ll*l*getsum(t1,l-1)-(getsum(t2,r)-getsum(t2,l-1));
}

 

二维树状数组

单点修改,子矩阵查询

//单点加
void add(int x,int y,int v){
    for(int i=x;i<=n;i+=lowbit(i))
        for(int j=y;j<=m;j+=lowbit(j))
            c[i][j]+=v;
}
//查询子矩阵和
int sum(int x,int y){
    int res=0;
    for(int i=x;i>0;i-=lowbit(i))
        for(int j=y;j>0;j-=lowbit(j))res+=c[i][j];
    return res;
}
int ask(int x1,int y1,int x2,int y2){
    return sum(x2,y2)-sum(x1-1,y2)-sum(x2,y1-1)+sum(x1-1,y1-1);
}

 

子矩阵加,求子矩阵和

 

 

 单点修改,查询全局第k小

权值数组:b[x]的值为x在a数组中出现的次数

该问题可离散化,如果原序列a的值域过大,可离散化后再建立权值数组b。

对于单点修改,只需将对原数列的单点修改转化为对权值数组的单点修改即可。

具体来说,原数组a[x]从y修改成z,转化为对权值数组b的单点修改就是b[y]单点减1,b[z]单点加1。

二分:时间复杂度是O(log 2n)

对于查询第k小,考虑二分x,查询权值数组中[1,x]的前缀和,找到x0使得[1,x0]的前缀和<k而[1,x0+1]的前缀和>=k,则第k大的数是x0+1。

 

倍增:时间复杂度是O(log n)

设x=0,sum=0,枚举i从log2n降为0:

· 查询权值数组中[x+1...x+2i]的区间和t

· 如果sum+t<k,扩展成功,x→x+2i,sum→sum+t;否则扩展失败,不操作

这样得到的x是满足[1...x]前缀和<k的最大值,所以最终答案是x+1。

int kth(int k){//对权值树状数组t查询第k小的数,n为数组t长度
    int sum=0,x=0;
    for(int i=log2(n);~i;--i){
        x+=1<<i;
        if(x>=n||sum+t[x]>=k)x-=1<<i;
        else sum+=t[x];
    }
    return x+1;
}

 

Trick

O(n)建树

方法一:

每一个节点的值都是由所有与自己相连的儿子的值求和所得到的,因此每次确定完儿子的值后用自己的值更新自己的直接父亲。

void init(){
    for(int i=1;i<=n;++i){
        t[i]+=a[i];
        int j=i+lowbit(i);
        if(j<=n)t[j]+=t[i];
    }
}

 

方法二:

c[i]表示的区间为 [ i - lowbit(i) + 1,i ],可以先预处理一个sun前缀和数组,用来计算c数组

void init(){
    for(int i=1;i<=n;++i){
        t[i]=sum[i]-sum[i-lowbit(i)+1];
    }
}

 

 


线段树

单点修改及区间和

int w[N];//每个数的值
struct Node{
    int l,r;
    int sum;//区间求和的值
}tr[4*N];

//pushup
void pushup(int u){
    tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum;
}

//建树
void build(int u,int l,int r){
    if(l==r)tr[u]={l,r,w[r]};
    else{
        tr[u]={l,r};
        int mid=l+r>>1;
        build(u<<1,l,mid),build(u<<1|1,mid+1,r);
        pushup(u);
    }
};

//单点修改:将w[x]改为v
void modify(int u,int x,int v){
    if(tr[u].l==x&&tr[u].r==x)tr[u]={x,x,v};
    else{
        int mid=tr[u].l+tr[u].r>>1;
        if(x<=mid)modify(u<<1,x,v);
        else modify(u<<1|1,x,v);
        pushup(u);
    }
}

//求[l,r]区间和
int query(int u,int l,int r){
    if(tr[u].l>=l&&tr[u].r<=r)return tr[u].sum;
    else{
        int mid=tr[u].l+tr[u].r>>1;
        if(r<=mid)return query(u<<1,l,r);
        else if(l>mid)return query(u<<1|1,l,r);
        else{
            int ll=query(u<<1,l,r);
            int rr=query(u<<1|1,l,r);
            int res=ll+rr;
            return res;
        }
    }
}

 

 

区间修改及区间查询

int w[N];//每个数的值
struct Node{
    int l,r;
    int sum;//区间求和的值
    int add;//懒标记
}tr[4*N];

//pushup
void pushup(int u){
    tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum;
}

//pushdown:除建树外,分裂都需要用
void pushdown(int u){
    Node &root=tr[u],&l=tr[u<<1],&r=tr[u<<1|1];
    l.add+=root.add,l.sum+=(l.r-l.l+1)*root.add;
    r.add+=root.add,r.sum+=(r.r-r.l+1)*root.add;
    root.add=0;
}

//建树
void build(int u,int l,int r){
    if(l==r)tr[u]={l,r,w[r],0};
    else{
        tr[u]={l,r};
        int mid=l+r>>1;
        build(u<<1,l,mid),build(u<<1|1,mid+1,r);
        pushup(u);
    }
};

//区间修改:区间[l,r]加d
void modify(int u,int l,int r,int d){
    if(tr[u].l>=l&&tr[u].r<=r){
        tr[u].sum+=(tr[u].r-tr[u].l+1)*d;//若范围过大,开ll
        tr[u].add+=d;
    }
    else{
        pushdown(u);
        int mid=tr[u].l+tr[u].r>>1;
        if(l<=mid)modify(u<<1,l,r,d);
        if(r>mid)modify(u<<1|1,l,r,d);
        pushup(u);
    }
}

//求[l,r]区间和
int query(int u,int l,int r){
    if(tr[u].l>=l&&tr[u].r<=r)return tr[u].sum;
    else{
        pushdown(u);
        int mid=tr[u].l+tr[u].r>>1;
        int res=0;
        if(l<=mid)res=query(u<<1,l,r);
        if(r>mid)res+=query(u<<1|1,l,r);
        return res;
    }
}

 

区间加乘,求区间和、乘积和

struct Segmen_Tree {
    tnode t[4 * N];
    void init_lazy(int root) {
        t[root].lazy[0] = 1, t[root].lazy[1] = 0;
    }
    void union_lazy(int fa, int ch) {
        int temp[2];
        //a = fa.a * ch.a
        temp[0] = t[fa].lazy[0] * t[ch].lazy[0] % mod;
        //b = fa.a * ch.b + fa.b
        temp[1] = ((t[fa].lazy[0] * t[ch].lazy[1] % mod) + t[fa].lazy[1]) % mod;
        t[ch].lazy[0] = temp[0];
        t[ch].lazy[1] = temp[1];
    }
    void cal_lazy(int root) {
        int len = (t[root].r - t[root].l + 1) % mod;
        //len * (len - 1) / 2 * b * b + a * a * sum[1] + a * b * (len - 1) * sum[0]
        t[root].sum[1] = (len * (len - 1) / 2 % mod * t[root].lazy[1] % mod * t[root].lazy[1] % mod +
                          t[root].lazy[0] * t[root].lazy[0] % mod * t[root].sum[1] % mod +
                          t[root].lazy[0] * t[root].lazy[1] % mod * (len - 1) % mod * t[root].sum[0] % mod) % mod;
        // len * b + a * sum[0]
        t[root].sum[0] = (len * t[root].lazy[1] % mod +
                          t[root].lazy[0] * t[root].sum[0] % mod) % mod;
        return;
    }
    void push_down(int root) {
        if (t[root].lazy[0] != 1 || t[root].lazy[1] != 0)
        {
            cal_lazy(root);
            if (t[root].l != t[root].r)
            {
                int ch = root << 1;
                union_lazy(root, ch);
                union_lazy(root, ch + 1);
            }
            init_lazy(root);
        }
    }
    void update(int root) {
        int ch = root << 1;
        push_down(ch);
        push_down(ch + 1);
        t[root] = t[ch] + t[ch + 1];
    }
    void build(int root, int l, int r) {
        t[root].l = l;
        t[root].r = r;
        init_lazy(root);
        if (l != r)
        {
            int mid = (l + r) >> 1;
            int ch = root << 1;
            build(ch, l, mid);
            build(ch + 1, mid + 1, r);
            update(root);
        }
        else
        {
            t[root].sum[0] = A[l] % mod;
            t[root].sum[1] = 0;
        }
    }
    void change(int root, int l, int r, int delta, int op) {
        push_down(root);
        if (l == t[root].l && r == t[root].r)
        {
            t[root].lazy[op] = delta % mod;
            return;
        }
        int mid = (t[root].l + t[root].r) >> 1;
        int ch = root << 1;
        if (r <= mid)change(ch, l, r, delta, op);
        else if (l > mid)change(ch + 1, l, r, delta, op);
        else {change(ch, l, mid, delta, op); change(ch + 1, mid + 1, r, delta, op);}
        update(root);
    }
    tnode sum(int root, int l, int r) {
        push_down(root);
        if (t[root].l == l && t[root].r == r)
        {
            return t[root];
        }
        int mid = (t[root].l + t[root].r) >> 1;
        int ch = root << 1;
        if (r <= mid)return sum(ch, l, r);
        else if (l > mid)return sum(ch + 1, l, r);
        else return sum(ch, l, mid) + sum(ch + 1, mid + 1, r);
    }
};

Segmen_Tree ST;
int n, m, op, l, r, x;

 

ST表

维护区间信息:区间最大,区间最小,区间按位和,区间按位或,区间gcd

RMQ( Range Maximum/Minimum Query ),求区间最大或最小值

void init(){
    for(int i=1;i<=n;++i)f[i][0]=a[i];
    for(int i=2;i<=n;++i)Log[i]=Log[i/2]+1;
    for(int i=1;i<=Log[n];++i)
        for(int j=1;j+(1<<i)-1<=n;++j)
            f[j][i]=max(f[j][i-1],f[j+(1<<(i-1))][i-1]);
}

 

int query(int l,int r){
    int len=Log[r-l+1];
    return max(f[l][len],f[r-(1<<len)+1][len]);
}

 

倍增

核心式子:to[x][i]=to[to[x][i-1]][i-1],代表从x开始跳2i步后的位置,先跳2i-1步,再跳2i-1

val[x][i]=val[x][i-1]+val[to[x][i-1]][i-1],表示从x开始跳2i步后得到的总值

    for(int i=1;i<=n;++i)val[i][0]=a[i];

    for(int i=1;i<=m;++i)
        for(int j=1;j<=n;++j){
            to[j][i]=to[to[j][i-1]][i-1];
            val[j][i]=val[j][i-1]+val[to[j][i-1]][i-1];
        }

 

 

倍增LCA

vector<int>g[N];//
vector<int>dep(N);//深度
vector<vector<int>>f(N,vector<int>(20+5));//f[i][j]表示从i向祖先走2^j步

void dfs(int u){
    for(int i=0;i<g[u].size(); ++i){
        int to=g[u][i];
        if(dep[to])continue;
        f[to][0]=u;
        dep[to]= dep[u] + 1;
        for(int j=1;j<=20;++j)f[to][j]=f[f[to][j-1]][j-1];
        dfs(to);
    }
}

int lca(int a,int b){//求a和b的最近公共祖先
    if(dep[a]<dep[b])swap(a,b);
    for(int i=20;i>=0;--i)
        if(dep[f[a][i]]>=dep[b])a=f[a][i];
    if(a==b){//a走到b
        return a;
    }
    
    //a和b到同一层后,一起向祖先走
    for(int i=20;i>=0;--i){
        if(f[a][i]!=f[b][i]){
            a=f[a][i],b=f[b][i];
        }
    }
    return f[a][0];
}

void init(){
    dep[1]=1;//根为1
    dfs(1);
}

 

posted @ 2023-02-12 19:45  bible_w  阅读(20)  评论(0编辑  收藏  举报