2018年全国多校算法寒假训练营练习比赛(第五场) (树状数组模板)



  题目: 点击打开链接 

    一共做了没几个题,感觉可以理解为这次是一个树状数组专题了。开始是不想用树状数组的,因为忘了大半了,在家里面手头又没有带笔记,只能找自己原来的代码和博客回顾了,也幸好代码都有注释,看自己写的还挺熟悉。

    先总结下树状数组的大体内容:用logn的复杂度进行区间求和。然后涉及到具体内容包括单点更新区间查询、区间更新单点查询、区间更新区间查询,还有处理问题常见的就是逆序数了。前两个都比较简单看一眼就能想起来,区间更新区间查询的稍微难一点,一直也是糊里糊涂的状态,还是要理解过程记住代码。

     关于逆序数,思路是重点,用结构体存点的下标id和值v,按照v排序,用b数组将v进行离散化存起来;再考虑到一个新数组c,代表一个长长的数轴,按照下标id的顺序将b的元素(也就是离散后的值v)存入,通过树状数组的方式操作c,求得(i-sum(b[i])),i是已存了多少个元素,sum(b[i])是在b[i]左边也就是比b[i]小的元素个数,做一个减法就是比b[i]大的元素个数,也就是逆序数,用sum求和即的结果。

    这几个题都没有什么变形,直接可以当做模板的:


B:单点查询,区间求和

#include<iostream>
#include<cstring>
using namespace std;
const int MAX=100010;
int c[MAX];
int N, M;
inline int lowBit(int x)
{
    return x & (-x);
}
void add(int pos, int v)
{
    while(pos<=N+5)
    {
        c[pos]+=v;
        pos+=lowBit(pos);
    }
}
int getSum(int pos)
{
    int sum=0;
    while(pos)
    {
        sum+=c[pos];
        pos-=lowBit(pos);
    }
    return sum;
}
void addInterval(int l, int r, int v)
{
    add(l, v);
    add(r+1, -v);
}
int queryInterval(int l, int r)
{
    return getSum(r)-getSum(l-1);
}
int main()
{
    int x, a, b;
    char ch;
    while(cin>>N>>M)
    {
        memset(c, 0, sizeof(c));
        for(int i=1; i<=N; i++)
        {
            cin>>x;
            add(i, x);

        }
        for(int i=0; i<M; i++)
        {
            cin>>ch;
            if(ch=='C')
            {
                cin>>a>>b>>x;
                addInterval(a, b, x);
            }
            else
            {
                cin>>a>>b;
                cout<<queryInterval(a, b)<<endl;
            }
        }
    }
    return 0;
}

H:区间更新,区间求和

#include <iostream>
#include<cstring>
using namespace std;
const int MAX=100010;
int B[MAX], C[MAX];
inline int lowBit(int x)
{
    return x & (-x);
}
void updata1(int *a, int pos, int v)//向上更新
{
    while(pos && pos<MAX)
    {
        a[pos]+=v;
        pos+=lowBit(pos);
    }
}
int query1(int *a, int pos)//向下求和
{
    int sum=0;
    while(pos && pos<MAX)
    {
        sum+=a[pos];
        pos-=lowBit(pos);
    }
    return sum;
}
void updata2(int *a, int pos, int v)//向下更新
{
    while(pos && pos<MAX)
    {
        a[pos]+=v;
        pos-=lowBit(pos);
    }
}
int query2(int *a, int pos)//向上求和
{
    int sum=0;
    while(pos && pos<MAX)
    {
        sum+=a[pos];
        pos+=lowBit(pos);
    }
    return sum;
}
inline void Insert(int pos, int v)//[1,p]加上d
{
    updata1(B, pos, pos*v);//B【】向上更新 [p,MAX]加上 p*d
    updata2(C, pos-1, v);//C【】向下更新 [1,p-1]加上 d
}
inline int Query(int pos)//求和
{
    return query1(B, pos)+query2(C, pos)*pos;
}
inline void insertSeg(int a, int b, int v)//[a,b]加上d  区间修改
{
    Insert(a-1, -v);//
    Insert(b, v);
}
inline int querySeg(int a, int b)//[a,b]求和    区间查询
{
    return Query(b)-Query(a-1);
}
int main()
{
    int x, a, b;
    int N, M;
    char ch;
    while(cin>>N>>M)
    {
        memset(C, 0, sizeof(C));
        memset(B, 0, sizeof(B));
        for(int i=2; i<=N+1; i++)
        {
            cin>>x;
            insertSeg(i, i, x);
        }
        for(int i=0; i<M; i++)
        {
            cin>>ch;
            if(ch=='C')
            {
                cin>>a>>b>>x;
                a++;    b++;
                insertSeg(a, b, x);
            }
            else
            {
                cin>>a>>b;
                a++;    b++;
                cout<<querySeg(a, b)<<endl;
            }
        }
    }
    return 0;
}

A:求逆序数(应用)

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
const int MAXN=100010;
int c[MAXN];
int lowbit(int x)
{
    return x & (-x);
}
int sum(int x)
{
    int sum=0;
    while(x>0)
    {
        sum+=c[x];
        x-=lowbit(x);
    }
    return sum;
}
void add(int x, int v)
{
    while(x<=MAXN)
    {
        c[x]+=v;
        x+=lowbit(x);
    }
}
struct node
{
    int v;
    int index;
    bool operator <(const node& b)const
    {
        return v<b.v;
    }
}nodes[MAXN];
int b[MAXN];//将初始数组重新赋值后 相对大小不变的新数组
int main()
{
    int n;
    while(cin>>n)
    {
        for(int i=1; i<=n; i++)
        {
            scanf("%d", &nodes[i].v);
            nodes[i].index=i;
        }
        sort(nodes+1, nodes+n+1);//按大小排序
        memset(b, 0, sizeof(b));
        b[nodes[1].index]=1;
        for(int i=2; i<=n; i++)//离散化
        {
            if(nodes[i].v==nodes[i-1].v)
                b[nodes[i].index]=b[nodes[i-1].index];
            else
                b[nodes[i].index]=i;//这里的下标还是原来东西的下标
        }
        memset(c, 0, sizeof(c));
        long long ans=0;
        for(int i=1; i<=n; i++)
        {
            add(b[i], 1);        //当前扫描的值是b[i],那么在x[b[i]]这个点上加1,表示又出现了1个b[i]值
            ans+=(i-sum(b[i]));// i当前插入几个数   sum(b[i])比b[i]小的数的个数    相减得逆序数
            //ans += sum(n)-sum(b[i]);
            //cout<<i-sum(b[i])<<"   "<<i<<endl;
        }
        cout<<ans<<endl;
    }
}






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值