树状数组(2019.7.22训练)

洛谷 P3374 【模板】树状数组 1

单点修改,区间查询和。

#include <bits/stdc++.h>
using namespace std;
const int N=5e5+10;
int n,m,x,y,opt,a[N];
void update(int i,int v)
{
    while(i<=n)
    {
        a[i]=a[i]+v;
        i=i+(i&-i);
    }
}
int sum(int i)
{
    int s=0;
    while(i)
    {
        s=s+a[i];
        i=i-(i&-i);
    }
    return s;
}
int main()
{
    ios::sync_with_stdio(false);
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        cin>>x;
        update(i,x);
    }
    while(m--)
    {
        cin>>opt>>x>>y;
        if(opt==1)update(x,y);
        else printf("%d\n",sum(y)-sum(x-1));
    }
    return 0;
}

洛谷 P3368 【模板】树状数组 2

区间修改,单点查询。维护差分数组即可。

#include <bits/stdc++.h>
using namespace std;
const int N=5e5+10;
int n,m,x,y,k,opt,a[N],ans[N];//原数组为a[i],其差分数组保存在树状数组ans[i]中
void update(int i,int v)
{
    while(i<=n)
    {
        ans[i]=ans[i]+v;
        i=i+(i&-i);
    }
}
int sum(int i)
{
    int s=0;
    while(i)
    {
        s=s+ans[i];
        i=i-(i&-i);
    }
    return s;
}
int main()
{
    ios::sync_with_stdio(false);
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    for(int i=1;i<=n;i++)
    {
        x=a[i]-a[i-1];//求差分
        update(i,x);
    }
    while(m--)
    {
        cin>>opt;
        if(opt==1)
        {
            cin>>x>>y>>k;
            update(x,k);
            update(y+1,-k);
        }
        else
        {
            cin>>x;
            printf("%d\n",sum(x));
        }
    }
    return 0;
}

LOJ 10117「一本通 4.1 练习 2」简单题

和上题类似,区间修改,单点询问。
用树状数组tr[]维护差分数组,从而记录区间内每个点反转的次数。
原数组初始值全为0,则其差分数组初始值也全为0,sum(i)表示差分数组1~i的前缀和,即i点反转的次数。
[l,r]内所有点的反转次数加1,等价于差分数组的tr[l]+1且tr[r+1]-1。

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,m,x,l,r,opt,tr[N];
void add(int i,int k)
{
    while(i<=n)
    {
        tr[i]+=k;
        i+=(i&-i);
    }
}
int sum(int i)
{
    int s=0;
    while(i)
    {
        s+=tr[i];
        i-=(i&-i);
    }
    return s;
}
int main()
{
    ios::sync_with_stdio(false);
    while(cin>>n>>m)
    {
        memset(tr,0,sizeof(tr));
        while(m--)
        {
            cin>>opt;
            if(opt==1)
            {
                cin>>l>>r;
                add(l,1);
                if(r+1<=n)add(r+1,-1);
            }
            else
            {
                cin>>x;
                if(sum(x)&1)printf("1\n");
                else printf("0\n");
            }
        }
    }
    return 0;
}

nefu 480 询问区间和

注意a[i]可能等于0,所以当顾客买走a[i]时,不能令a[i]=0当成标记数组用(价值为0的珠宝被买走了也要输出“0”,而不是输出“Sorry”)。要另外开一个标记数组。
还有就是注意树状数组的下标只能从1开始,这题输入的下标是从0开始的,所以要把输入的下标+1。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;
ll tr[N];
int n,m,l,r,x,cas,opt,a[N],flag[N];
void add(int i,int k)
{
    while(i<=n)
    {
        tr[i]+=k;
        i+=(i&-i);
    }
}
ll sum(int i)
{
    ll s=0;
    while(i)
    {
        s+=tr[i];
        i-=(i&-i);
    }
    return s;
}
int main()
{
    ios::sync_with_stdio(false);
    while(cin>>n>>m)
    {
        memset(tr,0,sizeof(tr));
        memset(flag,0,sizeof(flag));
        for(int i=1;i<=n;i++)
        {
            cin>>a[i];
            add(i,a[i]);
        }
        printf("Case #%d:\n",++cas);
        while(m--)
        {
            cin>>opt;
            if(opt==0)
            {
                cin>>x;x++;
                if(flag[x])printf("Sorry\n");
                else
                {
                    printf("%d\n",a[x]);
                    add(x,-a[x]);
                    flag[x]=1;
                }
            }
            else
            {
                cin>>l>>r;
                l++;r++;
                printf("%lld\n",sum(r)-sum(l-1));
            }
        }
    }
    return 0;
}

nefu 479 神奇的字符串

树状数组维护两个字符串中相同的字符个数即可。

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
char c,s1[N],s2[N];
int n,m,p,l,r,id,opt,cas,tr[N];
void update(int i,int k)//在i位置加上一个数k
{
    while(i<=n)
    {
        tr[i]+=k;
        i+=(i&-i);
    }
}
int sum(int i)
{
    int s=0;
    while(i)
    {
        s+=tr[i];
        i-=(i&-i);
    }
    return s;
}
int main()
{
    ios::sync_with_stdio(false);
    while(cin>>n>>m)
    {
        memset(tr,0,sizeof(tr));
        for(int i=1;i<=n;i++)
            cin>>s1[i];
        for(int i=1;i<=n;i++)
        {
            cin>>s2[i];
            if(s1[i]==s2[i])update(i,1);
        }
        printf("Case #%d:\n",++cas);
        while(m--)
        {
            cin>>opt;
            if(opt==0)
            {
                cin>>l>>r;
                printf("%d\n",sum(r)-sum(l-1));
            }
            else
            {
                cin>>id>>p>>c;
                if(id==1)//把s1的p位置修改为c
                {
                    if(s1[p]==s2[p]&&c!=s1[p])update(p,-1);
                    if(s1[p]!=s2[p]&&c==s2[p])update(p,1);
                    s1[p]=c;
                }
                else//把s2的p位置修改为c
                {
                    if(s1[p]==s2[p]&&c!=s1[p])update(p,-1);
                    if(s1[p]!=s2[p]&&c==s1[p])update(p,1);
                    s2[p]=c;
                }
            }
        }
    }
    return 0;
}

洛谷 P1908 逆序对

把a[i]当作数轴上的下标, 将a[i]离散化后存入树状数组中,由于是按时间顺序存入的,所以大于当前遍历的a[i]的所有数均与a[i]构成逆序对,即sum(n)-sum(a[i])(这里的n是a[i]的最大值,因为a[i]经过了离散化,所以a[i]的最大值不超过n)。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5e5+10;
ll ans,tr[N];
int n,cnt,a[N],t[N];
void add(int i)
{
    while(i<=n)
    {
        tr[i]++;
        i+=(i&-i);
    }
}
ll sum(int i)
{
    ll s=0;
    while(i)
    {
        s+=tr[i];
        i-=(i&-i);
    }
    return s;
}
int main()
{
    ios::sync_with_stdio(false);
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        t[i]=a[i];
    }
    sort(t+1,t+n+1);
    cnt=unique(t+1,t+n+1)-t-1;//是-t-1
    for(int i=1;i<=n;i++)
    {//将a[i]离散化
        a[i]=lower_bound(t+1,t+cnt+1,a[i])-t;//是-t,不是-t-1
        add(a[i]);
        ans+=sum(n)-sum(a[i]);
    }
    printf("%lld\n",ans);
    return 0;
}

poj 2352 Stars

把x坐标离散化后存入树状数组维护即可。

#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
const int N=15010;
int n,y,cnt,x[N],t[N],tr[N],ans[N];
void add(int i,int k)
{
    while(i<=n)
    {
        tr[i]+=k;
        i+=(i&-i);
    }
}
int sum(int i)
{
    int s=0;
    while(i)
    {
        s+=tr[i];
        i-=(i&-i);
    }
    return s;
}
int main()
{
    ios::sync_with_stdio(false);
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>x[i]>>y;
        t[i]=x[i];
    }
    sort(t+1,t+n+1);
    cnt=unique(t+1,t+n+1)-t-1;
    for(int i=1;i<=n;i++)
    {
        x[i]=lower_bound(t+1,t+cnt+1,x[i])-t;//将x离散化
        ans[sum(x[i])]++;//第sum(x[i])级的个数+1
        add(x[i],1);
    }
    for(int i=0;i<n;i++)
        printf("%d\n",ans[i]);
    return 0;
}

洛谷 P2345 奶牛集会

想办法化简公式,去掉原公式中的max和绝对值符号。
第i头牛和其他的牛发出的声音 = 第i头牛的vi * (它之前牛的个数 * 它的坐标 - 它之前所有牛的坐标和) + 第i头牛的vi * (它之后所有牛的坐标和 - 它之后牛的个数 * 它的坐标)。
对于牛在某个坐标区间内的个数和牛的坐标和,我们可以用树状数组来维护。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e4+10;
int v,x,n;
ll ans,tre1[N],tre2[N];
struct node
{
    int v,x;
}a[N];
bool cmp(node a,node b)
{
    if(a.v==b.v)return a.x<b.x;
    return a.v<b.v;
}
void add1(int i,int k)//维护个数
{
    while(i<=N)//是i<=N不是i<=n
    {
        tre1[i]+=k;
        i+=(i&-i);
    }
}
ll cnt(int i)//维护个数
{
    ll s=0;
    while(i)
    {
        s+=tre1[i];
        i-=(i&-i);
    }
    return s;
}
void add2(int i,int k)//维护坐标和
{
    while(i<=N)//是i<=N不是i<=n
    {
        tre2[i]+=k;
        i+=(i&-i);
    }
}
ll sum(int i)//维护坐标和
{
    ll s=0;
    while(i)
    {
        s+=tre2[i];
        i-=(i&-i);
    }
    return s;
}
int main()
{
    ios::sync_with_stdio(false);
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>a[i].v>>a[i].x;
    sort(a+1,a+n+1,cmp);
    for(int i=1;i<=n;i++)
    {
        v=a[i].v;
        x=a[i].x;
        add1(x,1);
        add2(x,x);
        ans+=v*(x*cnt(x-1)-sum(x-1)+sum(N)-sum(x)-x*(cnt(N)-cnt(x)));
    }
    printf("%lld\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

nefu-ljw

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

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

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

打赏作者

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

抵扣说明:

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

余额充值