bzoj3343 教主的魔法 ( 分块 +二分)

83 篇文章 0 订阅

bzoj3343 教主的魔法

原题地址http://www.lydsy.com/JudgeOnline/problem.php?id=3343

题意:
由不超过1000的正整数组成的序列,标号1,2,3…… N。教主的魔法
有m个询问或操作,每次操作可以把闭区间[L, R](1≤L≤R≤N)内的数全部加上一个整数W,每次询问闭区间 [L, R] 内有多少数大于等于C。
数据范围
N≤1000000,Q≤3000,1≤W≤1000,1≤C≤1,000,000,000
题解:
Q<=3000,可以分块。
维护一个序列a,一个每个块内的数排序后的序列b,
每次操作,完整的块直接在相应的add上加上w,
首尾块暴力加,并且重构排序后的b数组的对应位置。
询问,完整的块直接在b数组的对应位置上二分查找第一个>=c-add的位置,
首尾块暴力统计>=c-add个数。

(%lld啊……)
代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#define LL long long
using namespace std;
const int N=1000005;
const int B=1005;
int n,m,l[B],r[B],s,bel[N];
LL a[N],b[N],add[B];
void build(int x)
{
    for(int i=l[x];i<=r[x];i++)
    b[i]=a[i];
    sort(b+l[x],b+r[x]+1);
}
int query(int x,LL c)
{
    c-=add[x];
    int lf=l[x]; int rg=r[x];
    if(b[rg]<c) return 0;
    if(b[lf]>=c) return rg-lf+1;
    while(lf+1<rg)
    {
        int mid=(lf+rg)>>1;
        if(b[mid]<c) lf=mid;
        else rg=mid; 
    }
    int pos;
    if(b[lf]>=c) pos=lf; else pos=rg;
    return r[x]-pos+1;
}
int main()
{
    scanf("%d%d",&n,&m);
    int s=sqrt(n);
    for(int i=1;i<=n;i++)
    {

        bel[i]=(i-1)/s+1;
        if(bel[i]!=bel[i-1])
        {l[bel[i]]=i; r[bel[i]-1]=i-1;} 
    }

    r[bel[n]]=n;
    for(int i=1;i<=n;i++)
    {scanf("%lld",&a[i]); b[i]=a[i];}
    for(int i=bel[1];i<=bel[n];i++) //sort(begin(),end())
    sort(b+l[i],b+r[i]+1);
    while(m--)
    {
        char opt[5]; int x,y; LL c;
        scanf("%s",opt);
        scanf("%d%d%lld",&x,&y,&c);
        if(opt[0]=='M')
        {
            if(bel[x]==bel[y])
            {
                for(int i=x;i<=y;i++) a[i]+=c;
                build(bel[x]);
            }
            else
            {
                for(int i=bel[x]+1;i<bel[y];i++) add[i]+=c;
                for(int i=x;i<=r[bel[x]];i++) a[i]+=c;
                for(int i=l[bel[y]];i<=y;i++) a[i]+=c;
                build(bel[x]); build(bel[y]);
            }
        }
        else
        {
            LL ans=0;
            if(bel[x]==bel[y])
            {
                c-=add[bel[x]];
                for(int i=x;i<=y;i++) if(a[i]>=c) ans++;
            }
            else
            {
                for(int i=bel[x]+1;i<bel[y];i++) ans+=query(i,c);
                for(int i=x;i<=r[bel[x]];i++) if(a[i]>=c-add[bel[x]]) ans++;
                for(int i=l[bel[y]];i<=y;i++) if(a[i]>=c-add[bel[y]]) ans++;
                printf("%lld\n",ans);   
            }
        }
    }
    return 0;
}

( 昨天考了道非常痛苦的启发式合并trie树+回收空间,今天考了个了精度奇怪的李超线段树,调死我了……)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值