树状数组训练

树状数组训练

A. Ultra-QuickSort

题意:
交换相邻元素使n个元素的序列成为升序

Sol:
求逆序对的数量

  • 逆序对(归并排序,树状数组)

Code:

  1. 归并排序
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;
typedef long long LL;
const int N = 5e5 + 10;
int n;
int a[N], tmp[N];
LL res;  // 答案可能会超过int
void merge_sort(int a[], int l, int r)
{
    if (l >= r) return;
    int mid = l + r >> 1;
    merge_sort(a, l, mid);
    merge_sort(a, mid + 1, r);
    int k = 0, i = l, j = mid + 1;
    while (i <= mid && j <= r)
        if (a[i] <= a[j]) tmp[k ++ ] = a[i ++ ];
        else {
            res += mid - i + 1;// l-mid为升序,故逆序对贡献就是这之间所有的数
            tmp[k ++ ] = a[j ++ ];
        }
    while (i <= mid) tmp[k ++ ] = a[i ++ ];
    while (j <= r) tmp[k ++ ] = a[j ++ ];
    for (i = l, j = 0; i <= r; i ++, j ++ ) a[i] = tmp[j];
}
int main(){
    while(scanf("%d", &n), n)
    {
        res = 0;
        for(int i = 1; i <= n; ++i)
            scanf("%d", &a[i]);
        merge_sort(a, 1, n);
        printf("%lld\n", res);
    } 
    return 0;
}

2.树状数组

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long LL;
const int N = 5e5+10;
int n, m, a[N], b[N];
int tr[N];

int find(int x)
{
    int l = 1, r = m;
    while( l < r)
    {
        int mid = l + r >> 1;
        if(b[mid] >= x) r = mid;
        else l = mid + 1;
    }
    return l;
}
// 树状数组的基本操作
void add(int x, int c)
{
    for(int i = x;  i <= m; i += (i & -i)) tr[i] += c;
}
int sum(int x)
{
    int res = 0;
    for(int i = x; i ; i -= (i & -i)) res += tr[i];
    return res;
}
void solve()
{
    memset(tr, 0, sizeof tr);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]), b[i] = a[i];
    //利用b数组对a数组进行离散化(排序去重)
    sort(b + 1, b + 1 + n);
    m = 1;
    for (int i = 2; i <= n; i ++ )
        if(b[i] != b[m] ) b[++m] = b[i];
    LL res = 0;
    // 通过a数组的值找打其在b数组的位置
    for (int i = 1; i <= n; i ++ )
    {
        int x = find(a[i]);
        res += sum(m) - sum(x); //前面大于这个数的数量
        add(x, 1); // 将x这个数插入树状数组
    }
    printf("%lld\n", res);
}
int main()
{
    while(scanf("%d", &n), n) solve();
}

B.敌兵布阵

题意:
n n n个营地,第 i i i个营地有 a i a_i ai个人,接下来有若干条命令,
- (1) Add i i i j j j, i i i j j j为正整数,表示第 i i i个营地增加 j j j个人( j j j不超过30)
- (2) Sub i i i j j j , i i i j j j为正整数,表示第 i i i个营地减少 j j j个人( j j j不超过30);
- (3) Query i i i j j j , i i i j j j为正整数, i ≤ j i \le j ij,表示询问第 i i i到第 j j j个营地的总人数;
- (4) End 表示结束,这条命令在每组数据最后出现;
Sol:
数组数组的基本操作:单点修改和区间和查询

Code:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
const int N = 500010;
int n, Cas;
int a[N];
int tr[N];
int lowbit(int x) { return x & -x; }
void add(int x, int c)
{
    for(int i = x; i <= n; i += lowbit(i)) tr[i] += c;
}
int sum (int x)
{
    int res = 0;
    for(int i = x; i ; i -= lowbit(i)) res += tr[i];
    return res;
}
void solve()
{
    memset(tr, 0, sizeof tr);
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i)
    {
        scanf("%d", &a[i]);
        add(i, a[i]);
    }
    char op[10];
    bool ok = false;
    while(1)
    {
        scanf("%s", op);
        if(*op == 'Q')
        {
            int l, r;
            scanf("%d%d", &l, &r);
            if(!ok) printf("Case %d:\n", ++Cas), ok = true;
            printf("%d\n", sum(r) - sum(l - 1));
        }
        else if(*op == 'A')
        {
            int l, r;
            scanf("%d%d", &l, &r);
            add(l, r);
        }
        else if(*op == 'S')
        {
            int l, r;
            scanf("%d%d", &l, &r);
            add(l, -r);
        }
        else { break; }
    
    }
}
int main()
{
    int T = 1;
    scanf("%d", &T);
    while( T -- )
        solve();
    return (0-0);
}

C.Color the ball

题意:
n n n个气球,编号为 1 ∼ n 1 \sim n 1n,然后 n n n次染色, 读入 a a a b b b表示 a a a b b b染色一次。输出每个气球染色的次数

Sol:
数组数组维护差分序列

  • 差分序列
    Code:
#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
int n, m, a[N], tr[N];
int lowbit(int x) { return x & -x; }
void add(int x, int c)
{
    for(int i = x; i <= n; i += lowbit(i)) tr[i] += c;
}

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

int main(){
    while(scanf("%d", &n), n)
    {
        memset(tr, 0, sizeof tr);
        for(int i = 1; i <= n; ++i)
        {
            int a, b;
            scanf("%d%d", &a, &b);
            add(a, 1);
            add(b + 1, -1);
        }
        for(int i = 1; i <= n; ++i)
            if(i > 1) printf(" %d", sum(i));
            else printf("%d", sum(i));
        puts("");
    }
    return 0;
}

D.Japan

题意:
n n n, m m m座岛中间相隔一条河流,之间有 k k k条桥,问这 k k k条桥有多少个交点

Sol:
k k k座桥,按照右端点升序。如果左端点出现逆序,将会存在交点。类似求逆序对的作贡献

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 5e5 + 10;
int n, m, k;
struct node{
    int x, y;
    bool operator < (const node& b) const{
        if(y == b.y) return x < b.x;
        return y < b.y;
    }
}a[N + 10];
LL tr[N + 10];
int lowbit(int x) { return x & -x; };
void add(int x, LL c)
{
    for(int i = x; i <= N; i += lowbit(i)) tr[i] += c;
}

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

int main(){
    int T;
    int Cas = 1;
    read(T);
    while(T -- )
    {
        memset(tr, 0, sizeof tr);
        read(n); read(m); read(k);
        for(int i = 1; i <= k; ++i)
        {
            read(a[i].x);read(a[i].y);
        }
        sort(a+1, a+1+k);
        LL ans = 0;
        for(int i = 1; i <= k; ++i)
        {
            ans += sum(N) - sum(a[i].x);
            add(a[i].x, 1LL);
        }
        printf("Test case %d: %lld\n", Cas ++, ans);
    }   
    return 0;
}

E.Matrix

题意:
有一个 n ∗ n n*n nn的矩阵,一开始都是0。有两个操作:

  • 1.C x 1 x_1 x1 y 1 y_1 y1 x 2 x_2 x2 y 2 y_2 y2 将左上角 ( x 1 , y 1 ) (x_1,y_1) (x1,y1)和右下角 ( x 2 , y 2 ) (x_2,y_2) (x2,y2)矩阵内元素反转。
  • 2.Q x x x y y y 查询 ( x , y ) (x,y) (x,y)处的值

Sol:
二维树状数组。记录每个元素的反转次数。

Code:

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
const int N = 1010;
int tr[N][N], t, n, q;
int lowbit(int x) { return x & -x; };

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

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 += tr[i][j];
    return res;
}

int main(){
    scanf("%d", &t);
    while(t--)
    {
        memset(tr, 0, sizeof tr);
        scanf("%d%d", &n, &q);
        while(q--)
        {
            char op[10];
            int x, y, x1, y1;
            scanf("%s", op);
            if(*op == 'C') {
                scanf("%d%d%d%d", &x, &y, &x1, &y1);
                add(x, y, 1);
                add(x1 + 1, y1 + 1, 1);
                add(x, y1 + 1, -1);
                add(x1+1, y, -1);
            }
            else {
                scanf("%d%d", &x, &y);
                printf("%d\n", sum(x, y) & 1);
            }
        }
        if(t) printf("\n");
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

W⁡angduoyu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值