训练---树状数组和线段树


线段树、树状数组

树状数组能够解决的问题,那么线段树一定能解决。
树状数组与线段树相比,树状数组的代码短、常数很小
树状数组( O ( l o g n ) O(logn) O(logn)
①给某个位置上的数加上一个数(如果要变成某个数的话,那么就先减去这个数,然后再加上你要变的这个数)
②求某一个前缀和
树状数组是一个在线做法(就是支持修改的做法),树状数组只能解决这个问题,可以支持 单点修改(①)和区间查询(②),那么基于这两个基本问题,我们就可以配合差分的思想然后来运用到区间修改和单点查询。

线段树
会比树状数组理解起来简单一些
在这里插入图片描述
操作:
①单点修改(只用修改信息需要变化的位置)
②区间查询

pushup:用子节点信息更新当前节点信息
build:在一段区间上初始化线段树
modify:修改
query:查询

线段树就是一个函数,树状数组就是一个比较复杂的函数

一、动态求连续区间和(线段树、树状数组)

任意门

树状数组写法

#include <bits/stdc++.h>
using namespace std;

const int N=100010;
int a[N],tr[N];
int n,m;

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

void add(int x,int v)//给第x个数加v,基本操作①
{
    for(int i=x;i<=n;i+=lowbit(i))
        tr[i]+=v;
}
int query(int x)//基础操作②
{
    int res=0;
    for(int i=x;i;i-=lowbit(i))
    res+=tr[i];
    return res;
}

int main()
{

    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    scanf("%d",&a[i]);

    for(int i=1;i<=n;i++)
        add(i,a[i]);
     while(m--)
     {
         int k,x,y;
         scanf("%d%d%d",&k,&x,&y);
         if(k==0)
           printf("%d\n",query(y)-query(x-1));
         else add(x,y);
     }
     return 0;
}


线段树写法

#include <bits/stdc++.h>
using namespace std;

const int N=100010;
int n,m;
int w[N];
struct node{
    int l,r;
    int sum;
}tr[N*4];

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);
    }
}

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

void modify(int u,int x,int v)
{
    if(tr[u].l==tr[u].r)
        tr[u].sum+=v;
    else{
        int mid=tr[u].r+tr[u].l >>1;
        if(x<=mid)modify(u<<1,x,v);
        else modify(u<<1|1,x,v);
    pushup(u);
           }
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d",&w[i]);
    build(1,1,n);
    int k,a,b;
    while(m--)
    {
        scanf("%d%d%d",&k,&a,&b);
        if(k==0)
        printf("%d\n",query(1,a,b));
        else modify(1,a,b);
    }
    return 0;
}

二、数星星(树状数组)

任意门
给定一个题目,最核心的就是抽象化这个具体的操作。
树状数组下标一定要从1开始,所以当题目所给的x不是从1开始的话,我们就要把他变一下。

这道题我们跟着他的输入开始枚举,因为每一次的输入的数据都是以y递增的顺序进行,所以,我们读到的每一次的y都是当前最高的,所以这样子就很好我们操作了。

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

using namespace std;

const int N=32010;
int n;
int tr[N],level[N];

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

int add(int x)
{
    for(int i=x;i<N;i+=lowbit(i))
        tr[i]++;
}

int sum(int x)
{
    int res=0;
    for(int i=x;i;i-=lowbit(i))
        res+=tr[i];
        return res;
}

int main()
{
    scanf("%d",&n);
    for(int i=0;i<n;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        x++;
        level[sum(x)]++;
        add(x);
    }
    for(int i=0;i<n;i++)
        printf("%d\n",level[i]);
    return 0;
}

三、数列区间最大值(线段树)

任意门
这道题其实可以用ST表来写的,但是,ST表不支持修改,但是线段树吧就支持修改。

有的时候在做dp问题的时候,我们就可以用线段树来进行加速,这样子的话,可以把复杂度从 O ( n ) O(n) O(n)降低到 O ( l o g n ) O(logn) O(logn)

线段树的确慢一些,使用需谨慎

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


using namespace std;

const int N=1e5+10;
int n,m;
int w[N];
struct Node{
    int l,r;
    int maxv;
}tr[N*4];

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);
        tr[u].maxv=max(tr[u<<1].maxv,tr[u<<1|1].maxv);
    }
}

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


int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d",&w[i]);
    build(1,1,n);
    int l,r;
    while(m--)
    {
        scanf("%d%d",&l,&r);
        printf("%d\n",query(1,l,r));
    }

    return 0;
}

四、小朋友排队(树状数组)

任意门
只能交换相邻的小朋友,其实也就是冒泡排序的一个变形。
这里有几个结论,假设逆序对的个数为k
①交换次数至少是k
②在冒泡排序中,每次交换必然是逆序对数量-1
所以可以得出,逆序对交换次数为k

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


using namespace std;
typedef long long LL;

const int N=1e6+10;
int n,m;
int h[N],tr[N];
int sum[N];

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

void add(int x,int v)
{
    for(int i=x;i<N;i+=lowbit(i))
    tr[i]+=v;
}

int query(int x)
{
    int res=0;
    for(int i=x;i;i-=lowbit(i))
        res+=tr[i];
    return res;
}

int main()
{
    scanf("%d",&n);
    for(int i=0;i<n;i++)
        scanf("%d",&h[i]),h[i]++;
    //求每个数前面有多少个数比它大
    for(int  i=0;i<n;i++)
    {
        sum[i]=query(N-1)-query(h[i]);//算得是在他的前面插了多少个比他大的数
        add(h[i],1);//树状数组存的是这个数有多少个
    }
    //每个数后面有多少个数比他小
    memset(tr,0,sizeof tr);
    for(int i=n-1;i>=0;i--)
    {
        sum[i]+=query(h[i]-1);
        add(h[i],1);
    }
    LL res=0;
    for(int i=0;i<n;i++)res+=(LL)sum[i]*(sum[i]+1)/2;//这个是高斯求和公式
    cout<<res<<endl;
    return 0;
}

五、油漆面积(线段树、扫描线)

任意门
这道题目是亚特兰蒂斯那道题目的简化版本,简化了一个离散化
像这类题目我们用扫描线法来做:
从每个垂直于矩形的竖边做一个垂直于x轴的线
在这里插入图片描述
这种方法比较容易处理的是覆盖问题。

扫描线:①数据量很大–>nlogn
②矩形斜着、三角形、圆形
这个时候我们就要计算几何来做了—>这个时候就是公式比较困难了
这道题非常特殊的是

struct {
int l,r;//左右边界
int cnt;//当前区间被覆盖的总次数
int len;//至少被覆盖一次的区间长度
}

这里的cnt和len都是不考虑父节点的情况下进行,永远只对上不对下。我们这里的线段树维护的是纵坐标。

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


using namespace std;

const int N=10010;

int n;
struct Segment
{
    int x,y1,y2;
    int k;
    bool operator<(const Segment &t)const
    {
        return x<t.x;
    }
}seg[N*2];

struct Node
{
    int l,r;
    int cnt,len;
}tr[N*4];

void pushup(int u)
{
    if(tr[u].cnt>0)tr[u].len=tr[u].r-tr[u].l+1;
    else if(tr[u].l==tr[u].r)tr[u].len=0;
    else tr[u].len=tr[u<<1].len+tr[u<<1|1].len;
}

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);
}

void modify(int u,int l,int r,int k)
{
    if(tr[u].l>=l&&tr[u].r<=r)
    {
        tr[u].cnt+=k;
        pushup(u);
    }
    else{
        int mid=tr[u].l+tr[u].r>>1;
        if(l<=mid)modify(u<<1,l,r,k);
        if(r>mid)modify(u<<1|1,l,r,k);
        pushup(u);
    }
}

int main()
{
    scanf("%d",&n);
    int m=0;
    for(int i=0;i<n;i++)
    {
        int x1,x2,y1,y2;
        scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
        seg[m++]={x1,y1,y2,1};
        seg[m++]={x2,y1,y2,-1};
    }
    sort(seg,seg+m);
    build(1,0,10000);
    int res=0;
    for(int i=0;i<m;i++)
    {
        if(i>0)res+=tr[1].len*(seg[i].x-seg[i-1].x);
        modify(1,seg[i].y1,seg[i].y2-1,seg[i].k);
    }
    printf("%d\n",res);
    
    return 0;
}

六、三体攻击(二分+前缀和)

任意门

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

using namespace std;

typedef long long LL;

const int N = 2000010;

int A, B, C, m;
LL s[N], b[N], bp[N];
int d[8][4] = {
    {0, 0, 0, 1},
    {0, 0, 1, -1},
    {0, 1, 0, -1},
    {0, 1, 1, 1},
    {1, 0, 0, -1},
    {1, 0, 1, 1},
    {1, 1, 0, 1},
    {1, 1, 1, -1},
};
int op[N / 2][7];


int get(int i, int j, int k)
{
    return (i * B + j) * C + k;
}

bool check(int mid)
{
    memcpy(b, bp, sizeof b);
    for (int i = 1; i <= mid; i ++ )
    {
        int x1 = op[i][0], x2 = op[i][1], y1 = op[i][2], y2 = op[i][3], z1 = op[i][4], z2 = op[i][5], h = op[i][6];
        b[get(x1,     y1,     z1)]     -= h;
        b[get(x1,     y1,     z2 + 1)] += h;
        b[get(x1,     y2 + 1, z1)]     += h;
        b[get(x1,     y2 + 1, z2 + 1)] -= h;
        b[get(x2 + 1, y1,     z1)]     += h;
        b[get(x2 + 1, y1,     z2 + 1)] -= h;
        b[get(x2 + 1, y2 + 1, z1)]     -= h;
        b[get(x2 + 1, y2 + 1, z2 + 1)] += h;
    }

    memset(s, 0, sizeof s);
    for (int i = 1; i <= A; i ++ )
        for (int j = 1; j <= B; j ++ )
            for (int k = 1; k <= C; k ++ )
            {
                s[get(i, j, k)] = b[get(i, j, k)];
                for (int u = 1; u < 8; u ++ )
                {
                    int x = i - d[u][0], y = j - d[u][1], z = k - d[u][2], t = d[u][3];
                    s[get(i, j, k)] -= s[get(x, y, z)] * t;
                }

                if (s[get(i, j, k)] < 0) return true;
            }

    return false;
}

int main()
{
    scanf("%d%d%d%d", &A, &B, &C, &m);

    for (int i = 1; i <= A; i ++ )
        for (int j = 1; j <= B; j ++ )
            for (int k = 1; k <= C; k ++ )
                scanf("%lld", &s[get(i, j, k)]);

    for (int i = 1; i <= A; i ++ )
        for (int j = 1; j <= B; j ++ )
            for (int k = 1; k <= C; k ++ )
                for (int u = 0; u < 8; u ++ )
                {
                    int x = i - d[u][0], y = j - d[u][1], z = k - d[u][2], t = d[u][3];
                    bp[get(i, j, k)] += s[get(x, y, z)] * t;
                }

    for (int i = 1; i <= m; i ++ )
        for (int j = 0; j < 7; j ++ )
            scanf("%d", &op[i][j]);

    int l = 1, r = m;
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }

    printf("%d\n", r);

    return 0;
}

七、螺旋折线(数学找规律)

任意门

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

using namespace std;

typedef long long LL;

int main()
{
    int x, y;
    cin >> x >> y;

    if (abs(x) <= y)  // 在上方
    {
        int n = y;
        cout << (LL)(2 * n - 1) * (2 * n) + x - (-n) << endl;
    }
    else if (abs(y) <= x)  // 在右方
    {
        int n = x;
        cout << (LL)(2 * n) * (2 * n) + n - y << endl;
    }
    else if (abs(x) <= abs(y) + 1 && y < 0)  // 在下方
    {
        int n = abs(y);
        cout << (LL)(2 * n) * (2 * n + 1) + n - x << endl;
    }
    else  // 在左方
    {
        int n = abs(x);
        cout << (LL)(2 * n - 1) * (2 * n - 1) + y - (-n + 1) << endl;
    }

    return 0;
}

八、差分(一维差分)

任意门

#include <iostream>

using namespace std;

const int N = 100010;

int n, m;
int s[N];

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ ) cin >> s[i];
    for (int i = n; i; i -- ) s[i] -= s[i - 1];

    while (m -- )
    {
        int l, r, c;
        cin >> l >> r >> c;
        s[l] += c, s[r + 1] -= c;
    }

    for (int i = 1; i <= n; i ++ ) s[i] += s[i - 1];

    for (int i = 1; i <= n; i ++ ) cout << s[i] << ' ';
    cout << endl;

    return 0;
}


九、差分矩阵(二维差分)

任意门

#include <iostream>

using namespace std;

typedef long long ll;

const int N = 1010;
const int inf = 0x3f3f3f;

int n, m, q;
int a[N][N];

int main()
{
    scanf("%d%d%d", &n, &m, &q);

    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
        {
            int x;
            scanf("%d", &x);
            a[i][j] += x;
            a[i + 1][j] -= x;
            a[i][j + 1] -= x;
            a[i + 1][j + 1] += x;
        }

    while (q -- )
    {
        int x1, y1, x2, y2, c;
        scanf("%d%d%d%d%d", &x1, &y1, &x2, &y2, &c);
        a[x1][y1] += c;
        a[x2 + 1][y1] -= c;
        a[x1][y2 + 1] -= c;
        a[x2 + 1][y2 + 1] += c;
    }

    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
            a[i][j] += a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1];

    for (int i = 1; i <= n; i ++ )
    {
        for (int j = 1; j <= m; j ++ ) printf("%d ", a[i][j]);
        puts("");
    }

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值