0x42.数据结构进阶 - 树状数组

目录

  • 一、树状数组与逆序对
    • A、luogu P1908 逆序对(模板题)
    • B、AcWing 241. 楼兰图腾
  • 树状数组的拓展应用
    • 1.区间加,求单点值
    • A、AcWing 242. 一个简单的整数问题
    • 2.区间加,区间求和
    • B、AcWing 243. 一个简单的整数问题2
    • C、AcWing 244. 谜一样的牛

声明:
本系列博客是《算法竞赛进阶指南》+《算法竞赛入门经典》+《挑战程序设计竞赛》的学习笔记,主要是因为我三本都买了 按照《算法竞赛进阶指南》的目录顺序学习,包含书中的少部分重要知识点、例题解题报告及我个人的学习心得和对该算法的补充拓展,仅用于学习交流和复习,无任何商业用途。博客中部分内容来源于书本和网络(我尽量减少书中引用),由我个人整理总结(习题和代码可全都是我自己敲哒)部分内容由我个人编写而成,如果想要有更好的学习体验或者希望学习到更全面的知识,请于京东搜索购买正版图书:《算法竞赛进阶指南》——作者李煜东,强烈安利,好书不火系列,谢谢配合。


下方链接为学习笔记目录链接(中转站)

学习笔记目录链接


ACM-ICPC在线模板


0x42.数据结构进阶 - 树状数组_第1张图片

树状数组可以解决大部分基于区间上的更新以及求和问题。

也只能解决这些问题

对于树状数组初始化,我们可以直接遍历所有的点,并对所有的点update,这样的时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn),也可以用下面的方法 O ( n ) O(n) O(n)解决:
0x42.数据结构进阶 - 树状数组_第2张图片

一、树状数组与逆序对

树状数组求逆序对,时间复杂度为 O ( ( n + m l o g ) m ) O((n+mlog)m) O((n+mlog)m)

注意我们是在集合a的数值范围(y)上建立树状数组,来维护 t r e e tree tree的前缀和的,保存的是数值val在a数组里的出现次数。

A、luogu P1908 逆序对(模板题)

0x42.数据结构进阶 - 树状数组_第3张图片
在这里插入图片描述

本题的数据达到了1e9,所以我们应该离散化。
但是蓝书上说如果数据大到要用离散化,就得用sort,那么用树状数组还不如用归并排序效率更高。

#include 
#include 
#include 
#include 

using namespace std;
typedef long long ll;
const int N = 5000000;

int n,m;
struct node{
    int vis,id;
    bool operator<(const node &t)const{
        return vis < t.vis;
    }
}a[N];
int b[N];
int tree[N];

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

void update(int x,int k){
    for(;x <= n;x += lowbit(x))
        tree[x] += k;
}

ll ask(int x){
    ll res = 0;
    for(;x;x -= lowbit(x))
        res += tree[x];
    return res;
}

int main(){
    scanf("%d",&n);
    for(int i = 1;i <= n;++i){
        scanf("%d",&a[i].vis);
        a[i].id = i;
    }
    stable_sort(a + 1,a + 1 + n);
    for(int i = 1;i <= n;++i)
        b[a[i].id] = i;
    ll ans = 0;
    for(int i = n;i > 0;i -- ){
        ans += ask(b[i] - 1);
        update(b[i],1);
    }
    printf("%lld\n",ans);
    return 0;
}

B、AcWing 241. 楼兰图腾

题目链接
0x42.数据结构进阶 - 树状数组_第4张图片

‘v’图腾求法

倒序扫描序列a,利用树状数组求出每个a[i]后面有几个数比它大,记录为right[i]
正序扫描序列a,利用树状数组求出每个a[i]前面有几个数比它大,记录为left[i]
然后我们就可以枚举每一个点为中间点,那么这个点为中心的’v’图腾的个数就是. ∑ i = 1 N l e f t [ i ] × r i g h t [ i ] ∑^{N}_{i=1}left[i]×right[i] i=1Nleft[i]×right[i]

’^’图腾求法

倒序扫描序列a,利用树状数组求出每个a[i]后面有几个数比它小,记录为right[i]
正序扫描序列a,利用树状数组求出每个a[i]前面有几个数比它小,记录为left[i]
然后我们就可以枚举每一个点为中间点,那么这个点为中心的’^’图腾的个数就是. ∑ i = 1 N l e f t [ i ] × r i g h t [ i ] ∑^{N}_{i=1}left[i]×right[i] i=1Nleft[i]×right[i]

#include 
#include 
#include 
#include 
#include 

using namespace std;
typedef long long ll;
const int N = 500000;
int n,m;
int a[N];
int tree[N];

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

void update(int x,int val){
    for(;x <= n;x += lowbit(x))
        tree[x] += val;
}

ll ask(int x){//查询小于等于x的数的个数
    ll res = 0;
    for(;x;x -= lowbit(x))
        res += tree[x];
    return res;
}

int Greater[N],Lower[N];

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

    for(int i = 1;i <= n;++i){//正序求 左边 比自己小的个数或者大的个数
        int x = a[i];
        Greater[i] = ask(n) - ask(x);
        Lower[i] = ask(x - 1);
        update(x,1);
    }
    
    memset(tree,0,sizeof tree);

    ll res1 = 0;
    ll res2 = 0;

    for(int i = n;i > 0;i--){
        int x = a[i];
        res1 += Greater[i] * (ll)(ask(n) - ask(x));
        res2 += Lower[i] * (ll)ask(x - 1);
        update(x,1);
    }
    cout<<res1<<" "<<res2<<endl;
    return 0;
}

树状数组的拓展应用

1.区间加,求单点值

A、AcWing 242. 一个简单的整数问题

0x42.数据结构进阶 - 树状数组_第5张图片

#include 
#include 
#include 
#include 
#include 

using namespace std;

const int N = 500007;
typedef long long ll;
int n,m;
int a[N];
ll tree[N];

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

void update(int x,int val){
    for(;x <= n;x += lowbit(x))
        tree[x] += val;
}

ll ask(int x){
    ll res = 0;
    for(;x;x -= lowbit(x))
        res += tree[x];
    return res;
}

int main(){
    scanf("%d%d",&n,&m);
    for(int i = 1;i <= n;++i)
        scanf("%d",&a[i]),update(i,a[i] - a[i - 1]);
    for(int i = 1;i <= m;++i){
        char s[2];
        int l,r,d;
        scanf("%s%d",s,&l);
        if(*s == 'C'){
            scanf("%d%d",&r,&d);
            update(l,d),update(r + 1,-d);
        }
        else printf("%lld\n",ask(l));
    }
    return 0;
}

2.区间加,区间求和

B、AcWing 243. 一个简单的整数问题2

0x42.数据结构进阶 - 树状数组_第6张图片

0x42.数据结构进阶 - 树状数组_第7张图片

#include 
#include 
#include 
#include 
#include 

using namespace std;

const int N = 500007;
typedef long long ll;
int n,m;
int a[N];
ll tree[2][N],sum[N];

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

ll ask(int k,int x){
    ll res = 0;
    for(;x;x -= lowbit(x))
        res += tree[k][x];
    return res;
}

void add(int k,int x,int val){
    for(;x <= n;x += lowbit(x))
        tree[k][x] += val;
}

int main(){
    scanf("%d%d",&n,&m);
    for(int i = 1;i <= n;++i){
        scanf("%d",&a[i]);
        sum[i] = sum[i - 1] + a[i];
    }
    while(m--){
        char op[2];
        int l,r,d;
        scanf("%s%d%d",op,&l,&r);
        if(op[0] == 'C'){
            scanf("%d",&d);
            add(0,l,d);
            add(0,r + 1,-d);

            add(1,l,l * d);
            add(1,r + 1,-(r + 1) * d);
        }
        else {
            ll ans = sum[r] + (r + 1) * ask(0,r) - ask(1,r);
            ans -= sum[l - 1] + l * ask(0,l - 1) - ask(1,l - 1);
            printf("%lld\n",ans);
        }
    }
    return 0;
}

C、AcWing 244. 谜一样的牛

0x42.数据结构进阶 - 树状数组_第8张图片

树状数组+二分
我们发现,如果说第K头牛的前面有 A k Ak Ak头牛比它矮,那么它的身高Hk就是数值1 ~ n中第A k + 1 _k+1 k+1小的没有在 H k + 1 , H k + 2 , … , H n H_k+1,H_k+2,…,H_n Hk+1,Hk+2,,Hn中出现过的数
所以说,我们需要建立一个长度为n的1序列b,刚开始都是1,然后n到1倒序扫描每一个 A i A_i Ai,对于每个 A i A_i Ai执行查询和修改操作.
也就是说这道题目的题意就是让我们,动态维护一个01序列,支持查询第k个1所在的位置,以及修改序列中的一个数值

实时求出剩余的数中的第k小的数

#include 
#include 
#include 
#include 
#include 

using namespace std;

const int N = 500007;
typedef long long ll;
int n,m;
int a[N];
int ans[N];
int tree[N];

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

void update(int x,int val){
    for(;x <= n;x += lowbit(x))
        tree[x] += val;
}

int ask(int x){
    int res = 0;
    for(;x;x -= lowbit(x))
        res += tree[x];
    return res;
}

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

    for(int i = 2;i <= n;++i)
        scanf("%d",&a[i]);
    for(int i = 1;i <= n;++i)
        tree[i] = lowbit(i);
        //update(i,1);
    for(int i = n;i;i -- ){
        int k = a[i] + 1;//前面有k个,自己的答案就是k+1
        int l = 1,r = n;
        while(l < r){
                int mid = (l + r) >> 1;
            if(ask(mid) >= k)r = mid;
            else l = mid + 1;
        }
        ans[i] = r;
        update(r,-1);
    }
    for(int i = 1;i <= n;++i)
        printf("%d\n",ans[i]);
    return 0;
}

你可能感兴趣的:(【算法竞赛学习笔记】)