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;
}