基础数据结构

本文深入探讨了数据结构中的并查集和树状数组,这两种高效的数据结构在处理连通性、区间查询和修改等方面展现出强大的能力。并查集用于维护集合的连通性,支持快速合并和查询,而树状数组则能快速进行区间求和与修改,特别适用于动态维护区间信息。文章通过多个实例展示了它们在实际问题中的应用,如银河图腾、最大异或和等,帮助读者理解和掌握这些核心算法。
摘要由CSDN通过智能技术生成

数据结构

1. 并查集

  • 合并两个集合
  • 查询一个元素的祖宗节点
  • 询问两个元素是否在同一个集合中

具有信息传递性,对称性

// 朴素并查集:

int p[N]; //存储每个点的祖宗节点

// 返回x的祖宗节点 + 路径压缩
int find(int x)
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ ) p[i] = i;

// 合并a和b所在的两个集合:
p[find(a)] = find(b);

优化方式

  • 路径压缩 O ( log ⁡ n ) O(\log n) O(logn)
  • 按秩合并 O ( log ⁡ n ) O(\log n) O(logn)
  • 二者都采用 O ( α ( n ) ) O(\alpha(n)) O(α(n))

带权并查集

  • 记录每个集合的大小 绑定到根结点
  • 每个节点到根结点的距离(相对距离) 绑定到每个元素上
// 维护集合size的并查集:

int p[N], size[N];
//p[]存储每个点的祖宗节点, size[]只有祖宗节点的有意义,表示祖宗节点所在集合中的点的数量

// 返回x的祖宗节点
int find(int x)
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ )
{
    p[i] = i;
    size[i] = 1;
}

// 合并a和b所在的两个集合:
size[find(b)] += size[find(a)];
p[find(a)] = find(b);
// 维护到祖宗节点距离的并查集:

int p[N], d[N];
//p[]存储每个点的祖宗节点, d[x]存储x到p[x]的距离

// 返回x的祖宗节点
int find(int x)
{
    if (p[x] != x)
    {
        int root = find(p[x]);
        d[x] += d[p[x]];
        p[x] = root;
    }
    return p[x];
}

// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ )
{
    p[i] = i;
    d[i] = 0;
}

// 合并a和b所在的两个集合:
p[find(a)] = find(b);
d[find(a)] = distance; // 根据具体问题,初始化find(a)的偏移量

扩展域并查集(枚举)

1250. 格子游戏 - AcWing题库

并查集解决的是连通性(无向图联通分量)和传递性(家谱关系)问题,并且可以动态的维护。抛开格子不看,任意一个图中,增加一条边形成环当且仅当这条边连接的两点已经联通,于是可以将点分为若干个集合,每个集合对应图中的一个连通块。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
typedef long long ll;

const int MAXN = 40050;
int n, m, res = 1e9;
int a[MAXN], fa[MAXN];

int transfer(int x, int y)	// 二维转换为一维
{
    return (x-1) * n + y;
}

void init()
{
    for(int i = 1;i <= n*n;i++)
    {
        fa[i] = i;
    }
}

int find(int x)
{
    if(x != fa[x]) fa[x] = find(fa[x]);
    return fa[x];
}

int main()
{
    scanf("%d%d",&n,&m);
    init();
    for(int i = 1;i <= m;i++)
    {
        int x, y;
        char op;
        scanf("%d %d %c",&x,&y,&op);
        int res1, res2;
        
        res1 = transfer(x,y);
        if(op == 'D') res2 = transfer(x+1,y);
        else if(op == 'R') res2 = transfer(x,y+1);
        
        int u = find(res1), v = find(res2);
        if(u == v) {
            res = min(res,i);
        }
        else {
            fa[u] = v;
        }
    }
    if(res != 1e9) printf("%d\n",res);
    else puts("draw");
    return 0;
}

1252. 搭配购买 - AcWing题库

并查集+01背包

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;
typedef long long ll;
const int MAXN = 10010;
int fa[MAXN], c[MAXN], d[MAXN], dp[MAXN];
int n, m, w, id;

struct node{
    int c, d;
}Node[MAXN];

int find(int x)
{
    if(x != fa[x]) fa[x] = find(fa[x]);
    return fa[x];
}

void init()
{
    for(int i = 1;i <= n;i++) fa[i] = i;
}

int main()
{
    scanf("%d%d%d",&n,&m,&w);
    init();
    for(int i = 1;i <= n;i++)
    {
        scanf("%d%d",&c[i],&d[i]);
    }
    for(int i = 1;i <= m;i++)
    {
        int a, b;
        scanf("%d%d",&a,&b);
        int u = find(a), v = find(b);
        
        if(u != v)
        {
            fa[v] = u;
            c[u] += c[v];
            d[u] += d[v];
        }
    }
    
    for(int i = 1;i <= n;i++) {
        if(i == fa[i])
        {
            Node[id].c = c[i];
            Node[id].d = d[i];
            id++;
        }
    }
    
    for(int i = 0;i < id;i++)
    {
        for(int j = w;j >= Node[i].c;j--)
        {
            dp[j] = max(dp[j],dp[j-Node[i].c]+Node[i].d);
        }
    }
    int ans = dp[w];
    printf("%d\n",ans);
    return 0;
}

237. 程序自动分析 - AcWing题库

并查集+离散化

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>

using namespace std;
typedef long long ll;
const int MAXN = 2e6+50;

int n, T, id, cnt;
int fa[MAXN];

vector<pair<int,int>>equ, iequ;

int all[MAXN];

void init()
{
    for(int i = 0;i <= cnt;i++) fa[i] = i;
}

int search(int x)
{
    int res = lower_bound(all,all+cnt,x)-all;
    return res;
}

int find(int x)
{
    if(x != fa[x]) fa[x] = find(fa[x]);
    return fa[x];
}

int main()
{
    scanf("%d",&T);
    while(T--)
    {
        equ.clear();
        iequ.clear();
        bool flag = true;
        scanf("%d",&n);
        for(int k = 0;k < n;k++)
        {
            int i, j, e;
            scanf("%d%d%d",&i,&j,&e);
            if(e == 1) equ.push_back(make_pair(i,j));
            else iequ.push_back(make_pair(i,j));
            all[id++] = i;
            all[id++] = j;
        }
        sort(all,all+id);
        cnt = unique(all,all+id)-all;
        init();
        for(auto i : equ)
        {
            int x = search(i.first), y = search(i.second);
            int u = find(x), v = find(y);
            fa[u] = v;
        }
        for(auto i : iequ)
        {
            int x = search(i.first), y = search(i.second);
            int u = find(x), v = find(y);
            if(u == v) {
                flag = false;
                break;
            }
        }
        if(flag) puts("YES");
        else puts("NO");
    }
    return 0;
}

带边权的并查集

239. 奇偶游戏 - AcWing题库

利用前缀和的思想, S i = a 1 + a 2 + ⋯ + a i S_i = a_1 + a_2 + \dots + a_i Si=a1+a2++ai

S [ L , R ] S[L,R] S[L,R]中有奇数个1,则 S R S_R SR S L − 1 S_{L-1} SL1奇偶性不同

S [ L , R ] S[L,R] S[L,R]中有偶数个1,则 S R S_R SR S L − 1 S_{L-1} SL1奇偶性相同

采用离散化处理,只需找出第一次矛盾的地方

带权并查集: d [ x ] = 0 d[x]=0 d[x]=0表示与父节点同类,1表示不同类。描述的是一种相对的关系。(通过对2取模)

有几类就对几取模,通过两个点与根节点的长度来判断y两者之间关系。

判断x与y的关系, ( d [ x ] + d [ y ] ) m o d 2 = 0 (d[x]+d[y])mod2 = 0 (d[x]+d[y])mod2=0 说明 x x x y y y同类

① x,y同一类

  • f a [ x ] = f a [ y ] fa[x]=fa[y] fa[x]=fa[y] ( d [ x ] + d [ y ] ) m o d    2 = 0 (d[x]+d[y]) \mod 2= 0 (d[x]+d[y])mod2=0 不矛盾 ( d [ x ] + d [ y ] ) m o d    2 = 1 (d[x]+d[y])\mod 2= 1 (d[x]+d[y])mod2=1 矛盾
  • f a [ x ] ≠ f a [ y ] fa[x] \ne fa[y] fa[x]=fa[y] d [ f a [ x ] ] = ( d [ x ] + d [ y ] ) m o d    2 d[fa[x]] = (d[x] + d[y]) \mod 2 d[fa[x]]=(d[x]+d[y])mod2

① x,y不同一类

  • f a [ x ] = f a [ y ] fa[x]=fa[y] fa[x]=fa[y] ( d [ x ] + d [ y ] ) m o d    2 = 1 (d[x]+d[y]) \mod 2= 1 (d[x]+d[y])mod2=1 不矛盾 ( d [ x ] + d [ y ] ) m o d    2 = 0 (d[x]+d[y])\mod 2= 0 (d[x]+d[y])mod2=0 矛盾
  • f a [ x ] ≠ f a [ y ] fa[x] \ne fa[y] fa[x]=fa[y] d [ f a [ x ] ] = ( d [ x ] + d [ y ] + 1 ) m o d    2 d[fa[x]] = (d[x] + d[y] + 1) \mod 2 d[fa[x]]=(d[x]+d[y]+1)mod2

239. 奇偶游戏 - AcWing题库

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>

using namespace std;
const int MAXN = 10010;
int n, m;
int fa[MAXN], d[MAXN];
vector<int>all;
struct node{
    int l, r, flag;
}N[MAXN];
vector<node>v;

int find(int x)
{
    if(x != fa[x]) {
        int root = find(fa[x]);
        d[x] += d[fa[x]];
        fa[x] = root;
    }
    return fa[x];
}

int search(int x)
{
    return lower_bound(all.begin(),all.end(),x)-all.begin();
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i = 1;i <= m;i++)
    {
        int l, r;
        char op[7];
        scanf("%d%d%s",&l,&r,op);
        if(*op == 'e') v.push_back({l-1,r,0});
        else v.push_back({l-1,r,1});
        all.push_back(l-1);
        all.push_back(r);
    }
    sort(all.begin(),all.end());
    all.erase(unique(all.begin(),all.end()),all.end());
    
    int ans = 0;
    for(int i = 0;i < all.size();i++)
    {
        fa[i] = i;
        d[i] = 0;
    }
    
    for(auto i : v)
    {
        int u = search(i.l), v = search(i.r);
        if(i.flag)
        {
            int x = find(u), y = find(v);
            if(x == y)
            {
                if((d[u] + d[v]) % 2 == 0) break;
            }
            else
            {
                fa[x] = y;
                d[x] = (d[u] + d[v] + 1) % 2;
            }
        }
        else
        {
            int x = find(u), y = find(v);
            if(x == y)
            {
                if((d[u] + d[v]) % 2 == 1) break;
            }
            else
            {
                fa[x] = y;
                d[x] = (d[u] + d[v]) % 2;
            }
        }
        ans++;
    }
    printf("%d\n",ans);
    return 0;
}

238. 银河英雄传说 - AcWing题库

若要维护两两战舰之间的距离的话,复杂度为 O ( n 2 ) O(n^2) O(n2)级别。

因此统一维护该战舰到排头的距离

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;
const int MAXN = 30010;
int T;
int fa[MAXN], d[MAXN], sz[MAXN];
int find(int x)
{
    if(x != fa[x])
    {
        int root = find(fa[x]);
        d[x] += d[fa[x]];	// 完成路径压缩
        fa[x] = root;	
    }
    return fa[x];
}

int main()
{
    for(int i = 1;i <= 30000;i++)
    {
        fa[i] = i;
        d[i] = 0;	// 表示每个集合到根结点的距离
        sz[i] = 1;	// 表示每个集合大小
    }
    scanf("%d",&T);
    while(T--)
    {
        char op[2];
        int i, j;
        scanf("%s%d%d",op,&i,&j);
        if(*op == 'M')
        {
            int u = find(i), v = find(j);
            fa[u] = v;
            d[u] = sz[v];	// 每个顶点的距离加上原先的长度  等价于  排头结点加上集合大小
            sz[v] += sz[u];	
        }
        else
        {
            int u = find(i), v = find(j);
            if(u != v) printf("-1\n");
            else if(i == j) printf("0\n");
            else printf("%d\n",abs(d[i]-d[j])-1);
            
        }
    }
    return 0;
}

2. 树状数组

基本原理

  • 快速求前缀和 O ( log ⁡ n ) O(\log n) O(logn)
  • 修改某一个数 O ( log ⁡ n ) O(\log n) O(logn)

基于二进制优化

x = 2 i k + 2 i k = 1 + 2 i k − 2 + ⋯ + 2 i 1 log ⁡ x ≥ i k ≥ i k − 1 ≥ i k − 2 ≥ ⋯ ≥ i 1 x = 2^{i_k}+2^{i_{k=1}}+2^{i_{k-2}}+\dots +2^{i_1} \qquad \qquad \log x \ge i_k \ge i_{k-1} \ge i_{k-2} \ge \dots \ge i_1 x=2ik+2ik=1+2ik2++2i1logxikik1ik2i1

( x − 2 i 1 , x ] (x-2^{i_1},x] (x2i1,x]

( x − 2 i 1 − 2 i 2 , x − 2 i 1 ] (x-2^{i_1}-2^{i_2},x-2^{i_1}] (x2i12i2,x2i1]

( x − 2 i 1 − 2 i 2 − 2 i 3 , x − 2 i 1 − 2 i 2 ] (x-2^{i_1}-2^{i_2}-2^{i_3},x-2^{i_1}-2^{i_2}] (x2i12i22i3,x2i12i2]

… \dots

k ( x − 2 i 1 − 2 i 2 − ⋯ − 2 i k = 0 , x − 2 i 1 − 2 i 2 − ⋯ − 2 i k − 1 ] (x-2^{i_1}-2^{i_2}-\dots -2^{i_k} = 0,x-2^{i_1}-2^{i_2}-\dots -2^{i_{k-1}}] (x2i12i22ik=0,x2i12i22ik1]

发现区间 ( L , R ] (L,R] (L,R]的区间长度为R二进制的最后一位1 所对应的次幂 l o w b i t ( R ) lowbit(R) lowbitR

区间为 [ R − l o w b i t ( R ) + 1 , R ] [R-lowbit(R)+1,R] [Rlowbit(R)+1,R]

c [ x ] c[x] c[x]为区间 a [ x − l o w b i t ( x ) + 1 , x ] a[x-lowbit(x)+1,x] a[xlowbit(x)+1,x]所有数的和

c i = a i + a i − 1 + … … + a i − l o w b i t ( i ) + 1 c_i = a_i+a_{i-1}+……+a_{i-lowbit(i)+1} ci=ai+ai1++ailowbit(i)+1

c[1] = a[1]

c[2] = a[2]+a[1]

c[3] = a[3]

c[4] = a[4]+a[3]+a[2]+a[1]

9.8.JPG
树状数组查询 O ( log ⁡ n ) O(\log n) O(logn)

eg 查询a[1]+a[2]+……+a[7]

a[7] = c[7] a[6]+a[5] = c[6] a[4]+a[3]+a[2]+a[1] = c[4]

7 = ( 111 ) 2 (111)_2 (111)2 6 = ( 110 ) 2 (110)_2 (110)2 4 = ( 100 ) 2 (100)_2 (100)2 每次减去lowbit

// 查询a[1~x]的和
int sum(int x)
{
    int ret = 0;
    while(x)
    {
        ret += c[x];
        x -= lowbit(x);
    }
    return ret;
}
树状数组修改 O ( log ⁡ n ) O(\log n) O(logn)

x的父亲结点是x+lowbit(x)

每次修改会影响这个节点到根路径下的节点

// a[i] += k
void change(int i, int k)
{
    while(i <= n)
    {
        c[i] += k;
        i += lowbit(i);
    }
}
lowbit(i)求法
inline int lowbit(int x)
{
    return x & (-x);
}
inline int lowbit(int x)
{
    return x & (-x);
}

// 查询a[1~x]的和
int sum(int x)
{
    int ret = 0;
    while(x)
    {
        ret += c[x];
        x -= lowbit(x);
    }
    return ret;
}

// a[i] += k
void change(int i, int k)
{
    while(i <= n)
    {
        c[i] += k;
        i += lowbit(i);
    }
}

241. 楼兰图腾 - AcWing题库

树状数组维护出现过的数比其大or比其小的数量

从左向右依次遍历每个数a[i],使用树状数组统计在i位置之前所有比a[i]大的数的个数、以及比a[i]小的数的个数。统计完成后,将a[i]加入到树状数组。

从右向左依次遍历每个数a[i],使用树状数组统计在i位置之后所有比a[i]大的数的个数、以及比a[i]小的数的个数。统计完成后,将a[i]加入到树状数组。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

typedef long long ll;
const int MAXN = 2e5+50;
int a[MAXN], t[MAXN];
int small[MAXN], big[MAXN];
int n;

inline int lowbit(int x)
{
    return x & (-x);
}

int sum(int x)
{
    int res = 0;
    while(x)
    {
        res += t[x];
        x -= lowbit(x);
    }
    return res;
}

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

int main()
{
    scanf("%d",&n);
    for(int i = 1;i <= n;i++) scanf("%d",&a[i]);
    
    for(int i = 1;i <= n;i++)	// 顺序查找
    {
        big[i] = sum(n)-sum(a[i]);	// 找比a[i]大的数
        small[i] = sum(a[i]-1);	// 找比a[i]小的数
        add(a[i],1);
    }
    
    memset(t,0,sizeof(t));
    ll resv = 0, resa = 0;
    
    for(int i = n;i >= 1;i--)
    {
        resv += (ll)big[i]*(sum(n)-sum(a[i]));
        resa += (ll)small[i]*sum(a[i]-1);
        add(a[i],1);
    }
    printf("%lld %lld\n",resv,resa);
    
    return 0;
}

扩展

区间修改与单点询问
  • a [ L , R ] a[L,R] a[L,R]中每个元素+c
  • a [ x ] a[x] a[x]是多少

采用差分数组+树状数组

区间修改相当于差分数组中修改两个点

单点查询相当于差分数组求前缀和

242. 一个简单的整数问题 - AcWing题库

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
typedef long long ll;
const int MAXN = 100010;
int n, m;
ll a[MAXN], cf[MAXN], t[MAXN];

inline int lowbit(ll x)
{
    return x & (-x);
}

ll sum(int x)
{
    ll res = 0;
    while(x)
    {
        res += t[x];
        x -= lowbit(x);
    }
    return res;
}

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

int main()
{
    scanf("%d%d",&n,&m);
    for(int i = 1;i <= n;i++) scanf("%lld",&a[i]);
    for(int i = 1;i <= n;i++) {
        cf[i] = a[i]-a[i-1];
        add(i,cf[i]);
    }
    for(int i = 1;i <= m;i++)
    {
        char op[2];
        scanf("%s",op);
        if(*op == 'Q')
        {
            int x;
            scanf("%d",&x);
            printf("%lld\n",sum(x));
        }
        else
        {
            int l, r, d;
            scanf("%d%d%d",&l,&r,&d);
            add(l,d);
            add(r+1,-d);
        }
        
    }
    return 0;
}
区间修改与区间求和

区间修改 : 差分数组 a [ L , R ] + = c a[L,R] += c a[L,R]+=c 等价于 b [ R + 1 ] − = c b [ L ] − = c b[R+1] -= c \qquad b[L] -= c b[R+1]=cb[L]=c

区间求和 : a [ 1 ] + a [ 2 ] + a [ 3 ] + ⋯ + a [ x ] a[1]+a[2]+a[3]+\dots+a[x] a[1]+a[2]+a[3]++a[x] = ∑ i = 1 x a [ i ] = ∑ i = 1 x ∑ j = 1 i b [ j ] \sum_{i=1}^xa[i] = \sum_{i=1}^{x} \sum_{j=1}^i b[j] i=1xa[i]=i=1xj=1ib[j]

image-20210709120631174

考虑补集

a [ 1 ] + a [ 2 ] + a [ 3 ] + ⋯ + a [ x ] = ( x + 1 ) ∗ ( b [ 1 ] + b [ 2 ] + ⋯ + b [ x ] ) − ( 1 ∗ b [ 1 ] + 2 ∗ b [ 2 ] + 3 ∗ b [ 3 ] + ⋯ + x ∗ b [ x ] ) a[1]+a[2]+a[3]+\dots+a[x] = (x+1)*(b[1]+b[2]+\dots+b[x])-(1*b[1]+2*b[2]+3*b[3]+\dots+x*b[x]) a[1]+a[2]+a[3]++a[x]=(x+1)(b[1]+b[2]++b[x])(1b[1]+2b[2]+3b[3]++xb[x])

前面为 b [ i ] b[i] b[i]的前缀和,后面为 i ∗ b [ i ] i*b[i] ib[i]的前缀和

243. 一个简单的整数问题2 - AcWing题库

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
typedef long long ll;
const int MAXN = 1e5+50;
int n, m;
ll a[MAXN], t[MAXN], cf[MAXN], ti[MAXN];

inline int lowbit(int x)
{
    return x & (-x);
}

ll sum(int x)
{
    ll res = 0;
    while(x)
    {
        res += t[x];
        x -= lowbit(x);
    }
    return res;
}

ll sumi(int x)
{
    ll res = 0;
    while(x)
    {
        res += ti[x];
        x -= lowbit(x);
    }
    return res;
}

void add(int x, ll p)
{
    while(x <= n)
    {
        t[x] += p;
        x += lowbit(x);
    }
}

void addi(int x, ll p)
{
    while(x <= n)
    {
        ti[x] += p;
        x += lowbit(x);
    }
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i = 1;i <= n;i++) scanf("%lld",&a[i]);
    for(int i = 1;i <= n;i++) {
        cf[i] = a[i] - a[i-1];
        add(i,cf[i]);
        addi(i,i*cf[i]);
    }
    for(int i = 1;i <= m;i++)
    {
        char op[2];
        scanf("%s",op);
        if(*op == 'Q')
        {
            int l, r;
            scanf("%d%d",&l,&r);
            ll ans = ((r+1)*(sum(r))-sumi(r)) - ((l)*sum(l-1) - sumi(l-1));
            printf("%lld\n",ans);
        }
        else
        {
            int l, r, d;
            scanf("%d%d%d",&l,&r,&d);
            add(l,d);
            add(r+1,-d);
            addi(l,(ll)d*l);
            addi(r+1,-(ll)d*(r+1));
        }
    }
    return 0;
}

244. 谜一样的牛 - AcWing题库

每次查找第k小的数

并删除其

可用平衡树做。

若用树状数组,则将其全部初始化为1,每次二分查找sum(x)=k的最小x,将其减一

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
typedef long long ll;
const int MAXN = 1e5+50;
int a[MAXN], t[MAXN], ans[MAXN];
int n;

inline int lowbit(int x)
{
    return x & (-x);
}

int sum(int x)
{
    int res = 0;
    while(x)
    {
        res += t[x];
        x -= lowbit(x);
    }
    return res;
}

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

int main()
{
    scanf("%d",&n);
    a[1] = 0;
    for(int i = 2;i <= n;i++) scanf("%d",&a[i]);
    for(int i = 1;i <= n;i++) add(i,1);
    
    for(int i = n;i >= 1;i--)
    {
        int l = 1, r = n;
        while(l < r)
        {
            int mid = (l+r) >> 1;
            if(sum(mid) < a[i]+1) l = mid+1;
            else r = mid;
        }
        ans[i] = l;
        add(l,-1);
    }
    for(int i = 1;i <= n;i++) printf("%d\n",ans[i]);
    return 0;
}
二维树状数组
// a[x][y] += z;
int update(int x, int y, int z)
{
    int i = x;
    while(i <= n)
    {
        int j = y;
        while(j <= m)
        {
            c[i][j] += z;
            j += lowbit(j);
        }
        i += lowbit(i);
    }
}

// a[1][1] + …… + a[1][y] + a[2][1] + …… a[2][n] + …… +a[x][1] + …… + a[x][y]
int sum(int x, int y)
{
    int res = 0, i = x;
    while(i > 0)
    {
        j = y;
        while(j > 0)
        {
            res += c[i][j];
            j -= lowbit(j);
        }
        i -= lowbit(i);
    }
    return res;
}

3. 线段树

一个完全二叉树,每个节点代表一个区间。

区间 [ L , R ] [L,R] [L,R]被分为 [ L , m i d ] , [ m i d + 1 , R ] [L,mid],[mid+1,R] [L,mid],[mid+1,R] m i d = ⌊ L + R 2 ⌋ mid = \lfloor \frac{L+R}{2} \rfloor mid=2L+R

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u52lrHCn-1633831709717)(C:/Users/DELL/AppData/Roaming/Typora/typora-user-images/image-20210322084255533.png)]

线段树 当前结点x

父节点 ⌊ x 2 ⌋ \lfloor \frac{x}{2} \rfloor 2x

左儿子 x < < 1 x << 1 x<<1

右儿子 x < < 1 ∣ 1 x << 1 | 1 x<<11

最大范围 n < < 2 n << 2 n<<2

五个操作

  • pushup
  • pushdown
  • build
  • modify
  • query

node结构体

struct node{
    int l, r;
    ll sum, lazy;	// 区间[l,r]的和
};

int n, m;
int w[MAXN];
node tr[MAXN << 2];

pushup

// 求父节点的和
void pushup(int u)
{
    tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}

pushdown

void pushdown(int u)	// 将懒标记下传
{
    ll lazy = tr[u].lazy;
    if(lazy)
    {
        tr[u << 1].lazy += lazy;
        tr[u << 1 | 1].lazy += lazy;
        tr[u << 1].sum += (tr[u << 1].r - tr[u << 1].l + 1) * lazy;
        tr[u << 1 | 1].sum += (tr[u << 1 | 1].r - tr[u << 1 | 1].l + 1)*lazy;
        tr[u].lazy = 0; 	// 父节点的懒标记重置为0
    }
}

建树

//build(1,1,n) 建立区间1到n的线段树
void build(int u, int l, int r)
{
    if(l == r)
    {
        tr[u] = {l,r,w[r]};
        return;
    }
    else
    {
        tr[u] = {l,r,0};
        int mid = (l+r) >> 1;
        build(u << 1, l, mid);
        build(u << 1 | 1, mid+1, r);
        pushup(u);
    }
}

询问

// 求区间和[l,r]
// 调用(1,l,r)
ll query(int u, int l, int r)
{
    if(l <= tr[u].l && tr[u].r <= r) return tr[u].sum;
    else
    {
        pushdown(u);	// 下放懒标记
        int mid = (tr[u].l + tr[u].r) >> 1;
        ll sum = 0;
        if(l <= mid) sum += query(u << 1, l, r);
        if(r > mid) sum += query(u << 1 | 1, l, r);
        return sum;
    }
}

单点修改

// 调用(1,x,v)
// a[x] += v
void modify(int u, int x, int v)
{
    if(tr[u].l == tr[u].r) tr[u].sum += 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);
    }
}

区间修改

// a[l]~a[r] += d
void change(int u, int l, int r, int d)	// 区间修改
{
    if(l <= tr[u].l && tr[u].r <= r) //完全覆盖父节点,则标记一下懒惰标记即可,先不着急往下传,求和需要用到时再往下传
    {
        tr[u].lazy += d;
        tr[u].sum += (tr[u].r - tr[u].l + 1) * d;
        return;
    }
    else
    {
        pushdown(u);
        int mid = (tr[u].l + tr[u].r) >> 1;
        if(l <= mid) change(u << 1, l, r, d);
        if(r > mid) change(u << 1 | 1, l, r, d);
        pushup(u);
    }
}
struct node{
    int l, r;
    ll sum, lazy;	// 区间[l,r]的和
};

int n, m;
int w[MAXN];
node tr[MAXN << 2];

// 求父节点的和
void pushup(int u)
{
    tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}

void pushdown(int u)	// 将懒标记下传
{
    ll lazy = tr[u].lazy;
    if(lazy)
    {
        tr[u << 1].lazy += lazy;
        tr[u << 1 | 1].lazy += lazy;
        tr[u << 1].sum += (tr[u << 1].r - tr[u << 1].l + 1) * lazy;
        tr[u << 1 | 1].sum += (tr[u << 1 | 1].r - tr[u << 1 | 1].l + 1)*lazy;
        tr[u].lazy = 0; 	// 父节点的懒标记重置为0
    }
}

//build(1,1,n) 建立区间1到n的线段树
void build(int u, int l, int r)
{
    if(l == r)
    {
        tr[u] = {l,r,w[r]};
        return;
    }
    else
    {
        tr[u] = {l,r,0};
        int mid = (l+r) >> 1;
        build(u << 1, l, mid);
        build(u << 1 | 1, mid+1, r);
        pushup(u);
    }
}

// 求区间和[l,r]
// 调用(1,l,r)
ll query(int u, int l, int r)
{
    if(l <= tr[u].l && tr[u].r <= r) return tr[u].sum;
    else
    {
        pushdown(u);	// 下放懒标记
        int mid = (tr[u].l + tr[u].r) >> 1;
        ll sum = 0;
        if(l <= mid) sum += query(u << 1, l, r);
        if(r > mid) sum += query(u << 1 | 1, l, r);
        return sum;
    }
}

// 调用(1,x,v)
// a[x] += v
void modify(int u, int x, int v)
{
    if(tr[u].l == tr[u].r) tr[u].sum += 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);
    }
}

// a[l]~a[r] += d
void change(int u, int l, int r, int d)	// 区间修改
{
    if(l <= tr[u].l && tr[u].r <= r) //完全覆盖父节点,则标记一下懒惰标记即可,先不着急往下传,求和需要用到时再往下传
    {
        tr[u].lazy += d;
        tr[u].sum += (tr[u].r - tr[u].l + 1) * d;
        return;
    }
    else
    {
        pushdown(u);
        int mid = (tr[u].l + tr[u].r) >> 1;
        if(l <= mid) change(u << 1, l, r, d);
        if(r > mid) change(u << 1 | 1, l, r, d);
        pushup(u);
    }
}

1275. 最大数 - AcWing题库

维护区间最大值

模板题,单点修改,区间最大值。

区间最大值 = max(左区间最大值,右区间最大值)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
typedef long long ll;
const int MAXN = 2e5+50;
struct node{
    int l, r;
    int v;
}tr[MAXN*4];

int m, p;

void pushup(int u)
{
    tr[u].v = max(tr[u << 1].v,tr[u << 1 | 1].v);
}

void build(int u, int l, int r)
{
    tr[u] = {l,r};
    if(l == r) return;
    int mid = l + r >> 1;
    build(u << 1, l, mid);
    build(u << 1 | 1, mid+1, r);
}

int query(int u, int l, int r)
{
    if(tr[u].l >= l && tr[u].r <= r) return tr[u].v;
    else
    {
        int mid = tr[u].l + tr[u].r >> 1;
        int v = 0;
        if(l <= mid) v = query(u << 1, l, r);
        if(r > mid) v = max(v,query(u << 1 | 1, l, r));
        return v;    
    }
}

void modify(int u, int x, int v)
{
    if(tr[u].l == tr[u].r) tr[u].v = 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);
    }
}

int main()
{
    scanf("%d%d",&m,&p);
    build(1,1,m);
    int n = 0, last = 0;
    while(m--)
    {
        char op[3];
        scanf("%s",op);
        if(*op == 'A')
        {
            int t;
            scanf("%d",&t);
            modify(1,n+1,(t+last)%p);
            n++;
        }
        else
        {
            int l;
            scanf("%d",&l);
            last = query(1,n-l+1,n);
            printf("%d\n",last);
        }
    }
    return 0;
}

245. 你能回答这些问题吗 - AcWing题库

①单点修改

②查询区间的最大子段和 = max(左区间的最大子段和,右区间的最大子段和,l_max+r_max)

结构体中保存,区间左右端点,最大连续子段和,最大前缀和,最大后缀和,区间和。

查询的时候输出结构体

传引用

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
const int MAXN = 5e5 + 50;
int n, m;

int a[MAXN];
struct ST{
    int l, r;
    int lmax, rmax, tmax, sum;
}tr[MAXN * 4];

void pushup(ST &u, ST &l, ST &r)
{
    u.sum = l.sum + r.sum;
    u.lmax = max(l.lmax, l.sum + r.lmax);
    u.rmax = max(r.rmax, r.sum + l.rmax);
    u.tmax = max(max(l.tmax, r.tmax), l.rmax + r.lmax);
}

void pushup(int u)
{
    pushup(tr[u], tr[u << 1], tr[u << 1 | 1]);
}

void build(int u, int l, int r)
{
    tr[u] = {l, r};
    if(l == r)
    {
        tr[u].sum = tr[u].lmax = tr[u].rmax = tr[u].tmax = a[l];
        return;
    }
    int mid = (l + r) >> 1;
    build(u << 1, l, mid);
    build(u << 1 | 1, mid + 1, r);
    pushup(u);
}

ST query(int u, int l, int r)
{
    if(tr[u].l >= l && tr[u].r <= r) {
        return tr[u];
    }
    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 {
        ST root;
        auto left = query(u << 1, l, r);
        auto right = query(u << 1 | 1, l, r);
        pushup(root, left, right);
        return root;
    }
    
}

void change(int u, int x, int y)
{
    if(tr[u].l == x && tr[u].r == x) 
    {
        tr[u].sum = y;
        tr[u].lmax = y;
        tr[u].tmax = y;
        tr[u].rmax = y;
        return;
    }
    int mid = (tr[u].l + tr[u].r) >> 1;
    if(x <= mid) change(u << 1, x, y);
    else change(u << 1 | 1, x, y);
    pushup(u);
}

int main()
{
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
    build(1, 1, n);
    for(int i = 0; i < m; i++)
    {
        int op;
        scanf("%d", &op);
        if(op == 1)
        {
            int l, r;
            scanf("%d%d", &l, &r);
            if(l > r) swap(l, r);
            auto ans = query(1, l, r);
            printf("%d\n", ans.tmax);
        }
        else {
            int x, y;
            scanf("%d%d", &x, &y);
            change(1, x, y);
        }
    }
    return 0;
}

243. 一个简单的整数问题2 - AcWing题库

懒标记(区间修改和区间查询)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
typedef long long ll;
const int MAXN = 1e5+50;
int n, m;
ll a[MAXN];

struct node{
    int l, r;
    ll sum, lazy;
}tr[MAXN * 4];

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

void pushdown(int u)
{
    ll lazy = tr[u].lazy;
    if(lazy)
    {
        tr[u << 1].lazy += lazy;
        tr[u << 1 | 1].lazy += lazy;
        tr[u << 1].sum += (tr[u << 1].r - tr[u << 1].l + 1) * lazy;
        tr[u << 1 | 1].sum += (tr[u << 1 | 1].r - tr[u << 1 | 1].l + 1) * lazy;
        tr[u].lazy = 0;
    }
}

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

void change(int u, int l, int r, int d)
{
    if(l <= tr[u].l && tr[u].r <= r)
    {
        tr[u].lazy += d;
        tr[u].sum += (tr[u].r - tr[u].l + 1) * d;
        return;
    }
    else
    {
        pushdown(u);
        int mid = (tr[u].l + tr[u].r) >> 1;
        if(l <= mid) change(u << 1, l, r, d);
        if(r > mid) change(u << 1 | 1, l, r, d);
        pushup(u);
    }
}


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

int main()
{
    scanf("%d%d",&n,&m);
    for(int i = 1;i <= n;i++) scanf("%lld",&a[i]);
    build(1,1,n);
    while(m--)
    {
        char op[2];
        scanf("%s",op);
        if(*op == 'Q') {
            int l, r;
            scanf("%d%d",&l,&r);
            ll ans = query(1,l,r);
            printf("%lld\n",ans);
        }
        else {
            int l, r, d;
            scanf("%d%d%d",&l,&r,&d);
            change(1,l,r,d);
        }
    }
    return 0;
}

1277. 维护序列 - AcWing题库

维护区间修改的乘法和加法。 需要加法和乘法的懒标记

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
typedef long long ll;
const int MAXN = 1e5+50;
int n, p, T;
int a[MAXN];
struct node{
    int l, r;
    ll sum, add, mul; // mul是乘法懒标记 
}tr[MAXN * 4];

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

void pushdown(int u)
{
    ll add = tr[u].add, mul = tr[u].mul;
    tr[u << 1].add = (tr[u << 1].add * mul + add) % p;
    tr[u << 1 | 1].add = (tr[u << 1 | 1].add * mul + add) % p;
    tr[u << 1].mul = tr[u << 1].mul * mul % p;
    tr[u << 1 | 1].mul = tr[u << 1 | 1].mul * mul % p;
    tr[u << 1].sum = (tr[u << 1].sum * mul + (tr[u << 1].r - tr[u << 1].l + 1) * add) % p;
    tr[u << 1 | 1].sum = (tr[u << 1 | 1].sum * mul + (tr[u << 1 | 1].r - tr[u << 1 | 1].l + 1) * add) % p;
    tr[u].add = 0;
    tr[u].mul = 1;
}

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

void change(int u, int l, int r, int c, int a)
{
    if(l <= tr[u].l && tr[u].r <= r)
    {
        tr[u].sum = (tr[u].sum * c + a * (tr[u].r - tr[u].l + 1)) % p;
        tr[u].add = (tr[u].add * c + a) % p;
        tr[u].mul = (tr[u].mul * c) % p;
    }
    else
    {
        pushdown(u);
        int mid = (tr[u].l + tr[u].r) >> 1;
        if(l <= mid) change(u << 1, l, r, c, a);
        if(r > mid) change(u << 1 | 1, l, r, c, a);
        pushup(u);
    }
}

int query(int u, int l, int r)
{
    if(l <= tr[u].l && tr[u].r <= r)
    {
        return tr[u].sum;
    }
    pushdown(u);
    int mid = (tr[u].l + tr[u].r) >> 1;
    ll res = 0;
    if(l <= mid)
    {
        res += query(u << 1, l, r);
        res = res % p;
    }
    if(r > mid)
    {
        res += query(u << 1 | 1, l, r);
        res = res % p;
    }
    return res;
}

int main()
{
    scanf("%d%d",&n,&p);
    for(int i = 1;i <= n;i++) scanf("%d",&a[i]);
    build(1,1,n);
    scanf("%d",&T);
    while(T--)
    {
        int op, l, r, c;
        scanf("%d",&op);
        if(op == 1) {
            scanf("%d%d%d",&l,&r,&c);
            change(1, l, r, c, 0);
        }
        else if(op == 2) {
            scanf("%d%d%d",&l,&r,&c);
            change(1,l,r,1,c);
        }
        else{
            scanf("%d%d",&l,&r);
            int res = query(1,l,r);
            printf("%d\n",res);
        }   
    }
    return 0;
}

4. 可持久化数据结构

可持久化前提:在本身的操作过程中,数据结构的拓扑结构不变

Trie的可持久化

把每次更新的版本都存储下来,可存储数据结构的所有历史版本。

只记录每一个版本与前一个版本不一样的地方。

image-20210721201730986

256. 最大异或和 - AcWing题库

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

typedef long long ll;
const int MAXN = 6e5+50;
int s[MAXN], tr[MAXN*25][2], idx, max_id[MAXN*25], root[MAXN];
// root存储每次版本根的信息, max_id存储当前子树中最大的左端点
int n, m;

// 插入操作, 第i个数, 第k位, p是旧版本, q是新版本
void insert(int i, int k, int p, int q) 
{
    // 如果插到最后一位
    if(k < 0)
    {
        max_id[q] = i;
        return;
    }
    int u = s[i] >> k & 1;
    // 如果其他位有数,直接复制
    if(p) tr[q][!u] = tr[p][!u];
    // 当前位建立新结点
    tr[q][u] = ++idx;
    // 插入下一位
    insert(i,k-1,tr[p][u], tr[q][u]);
    // 递归后求子结点
    max_id[q] = max(max_id[tr[q][0]],max_id[tr[q][1]]);
}

int query(int root, int c, int l)
{
    int p = root;
    for(int i = 23;i >= 0;i--)
    {
        int u = c >> i & 1;
        if(max_id[tr[p][!u]] >= l) p = tr[p][!u]; // 如果当前位可以走到!u,就走
        else p = tr[p][u];
    }
    return c ^ s[max_id[p]];
}

int main()
{
    scanf("%d%d",&n,&m);
    // 插入根
    max_id[0] = -1;
    root[0] = ++idx;
    insert(0,23,0,root[0]);
    
    for(int i = 1;i <= n;i++)
    {
        int x;
        scanf("%d",&x);
        s[i] = s[i-1] ^ x;
        root[i] = ++idx;
        insert(i,23,root[i-1],root[i]);
    }
    
    while(m--)
    {
        char op[2];
        scanf("%s",op);
        if(*op == 'A')
        {
            int x;
            scanf("%d",&x);
            n++;
            s[n] = s[n-1] ^ x;
            root[n] = ++idx;
            insert(n,23,root[n-1],root[n]);
        }
        else
        {
            int l, r, x;
            scanf("%d%d%d",&l,&r,&x);
            int ans = query(root[r-1],s[n]^x,l-1); // 版本r-1,限制l-1
            printf("%d\n",ans);
        }
    }
    
    return 0;
}

线段树的可持久化 (主席树)

5. RMQ (ST表)

类似于倍增的动态规划

快速查询区间最大值,不支持修改

f [ i , j ] f[i, j] f[i,j]表示从 i i i开始,长度为 2 j 2^j 2j​的区间最大值

初始化:

f [ i , j ] = m a x ( f [ i , j − 1 ] , f [ i + 2 j − 1 , j − 1 ] ) f[i,j] = max(f[i, j-1], f[i+2^{j-1}, j-1]) f[i,j]=max(f[i,j1],f[i+2j1,j1])

查询 [ L , R ] [L,R] [L,R]

R − L + 1 = l e n R - L + 1 = len RL+1=len

k = ⌊ l o g 2 l e n ⌋ k = \lfloor log_2^{len} \rfloor k=log2len

f [ L , R ] = m a x ( f [ L , k ] , f [ R − 2 k + 1 , k ] f[L, R] = max(f[L, k], f[R-2^k+1, k] f[L,R]=max(f[L,k],f[R2k+1,k]

模板
const int MAXN = 2e5+50, N = 18;
int n, m;
int a[MAXN], dp[MAXN][N];

void init()
{
    for(int j = 0;j < N;j++)
    {
        for(int i = 1;i + (1 << j) - 1 <= n;i++)
        {
            if(j == 0) dp[i][j] = a[i];
            else dp[i][j] = max(dp[i][j-1], dp[i + (1 << j - 1)][j-1]);
        }
    }
}

int query(int l, int r)
{
    int len = r - l + 1;
    int k = log(len) / log(2);
    int ans = max(dp[l][k], dp[r - (1 << k) + 1][k]);
    return ans;
}

1273. 天才的记忆 - AcWing题库(模板题)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>

using namespace std;
const int MAXN = 2e5+50, N = 18;
int n, m;
int a[MAXN], dp[MAXN][N];

void init()
{
    for(int j = 0;j < N;j++)
    {
        for(int i = 1;i + (1 << j) - 1 <= n;i++)
        {
            if(j == 0) dp[i][j] = a[i];
            else dp[i][j] = max(dp[i][j-1], dp[i + (1 << j - 1)][j-1]);
        }
    }
}

int query(int l, int r)
{
    int len = r - l + 1;
    int k = log(len) / log(2);
    int ans = max(dp[l][k], dp[r - (1 << k) + 1][k]);
    return ans;
}

int main()
{
    scanf("%d",&n);
    for(int i = 1;i <= n;i++) scanf("%d",&a[i]);
    init();
    scanf("%d",&m);
    while(m--)
    {
        int l, r;
        scanf("%d%d",&l,&r);
        int ans = query(l, r);
        printf("%d\n",ans);
    }
    
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值