多校第十场:HDU 4973 树状数组+前缀+二分

思路:比赛的时候看到这题感觉是线段树或者树状数组,但是因为要区间加倍,然后不知所措了,想了好久也不知道怎么把那个加倍的数怎么处理,然后就一直想第一道题了就没管这题了。虽然之前也做过这种类型的树状数组,不过这题确实是比较机智,把树状数组用得是非常爽。

sum[i]是树状数组的前缀和,cnt[i]表示第i个数共有多少个数。

更新加倍过程:我们让(l,r)这个区间加倍的时候,二分树状数组找到l所对应的数,找到r对应的数,单点更新这两个数,为什么要单点更新这两个数呢?因为比如:1 1 1 2 2 2 3 3 3 4 4这个序列,令(l,r)=(2,8),2位置查找到1这个数,但是是从第2个位置开始的,所以只包含了两个1;8位置也只包含了两个3,并不是全部包含了所有的1和3,所以这端点位置为什么要单独更新就是这个原因。然后区间更新他们之间的数,比如上面这个序列,更新1和3之间的数就是2,2的所有数即总共3个数肯定是要全部加倍的。

查找也是一样。

#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
#include<queue>
#include<set>
#include<cmath>
#include<bitset>
#define mem(a,b) memset(a,b,sizeof(a))
#define lson i<<1,l,mid
#define rson i<<1|1,mid+1,r
#define llson j<<1,l,mid
#define rrson j<<1|1,mid+1,r
#define INF 0x7fffffff
#define maxn 50005
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
ll sum[maxn],cnt[maxn];
int n,m;
ll getsum(int x)
{
    ll ans=0;
    for(int i=x; i>0; i-=i&(-i))
        ans+=sum[i];
    return ans;
}
void add(int x,ll v)
{
    for(int i=x; i<=n; i+=i&(-i))
        sum[i]+=v;
}
int find(ll v)
{
    int l=1,r=n,mid;
    while(l<r)
    {
        mid=(l+r)>>1;
        if(getsum(mid)>=v) r=mid;
        else l=mid+1;
    }
    return r;
}
void update(ll l,ll r)
{
    int L=find(l),R=find(r);
    if(L==R)
    {
        add(L,r-l+1);
        cnt[L]+=r-l+1;
        return;
    }
    ll LL=getsum(L),RR=getsum(R-1);
    add(L,LL-l+1),add(R,r-RR);//端点单独更新
    cnt[L]+=LL-l+1,cnt[R]+=r-RR;
    for(int i=L+1;i<R;i++)
        add(i,cnt[i]),cnt[i]+=cnt[i];
}
ll query(ll l,ll r)
{
    int L=find(l),R=find(r);
    if(L==R) return r-l+1;
    ll Max=0;
    Max=getsum(L)-l+1;
    Max=max(Max,r-getsum(R-1));
    for(int i=L+1;i<R;i++)
        Max=max(Max,cnt[i]);
    return Max;
}
int main()
{
    //freopen("1.txt","r",stdin);
    int t,ii=1;
    char s[3];
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        for(int i=0;i<=n;i++) sum[i]=0;
        for(int i=1;i<=n;i++) add(i,1),cnt[i]=1;
        printf("Case #%d:\n",ii++);
        ll l,r;
        while(m--)
        {
            scanf("%s%I64d%I64d",s,&l,&r);
            if(s[0]=='D') update(l,r);
            else printf("%I64d\n",query(l,r));
        }
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值