[Noip模拟题]教主的魔法
题目
Description
教主最近学会了一种神奇的魔法,能够使人长高。于是他准备演示给XMYZ信息组每个英雄看。于是N个英雄们又一
次聚集在了一起,这次他们排成了一列,被编号为1、2、……、N。每个人的身高一开始都是不超过1000的正整数
。教主的魔法每次可以把闭区间[L, R](1≤L≤R≤N)内的英雄的身高全部加上一个整数W。(虽然L=R时并不符合
区间的书写规范,但我们可以认为是单独增加第L(R)个英雄的身高)CYZ、光哥和ZJQ等人不信教主的邪,于是他
们有时候会问WD闭区间 [L, R] 内有多少英雄身高大于等于C,以验证教主的魔法是否真的有效。WD巨懒,于是他
把这个回答的任务交给了你。
次聚集在了一起,这次他们排成了一列,被编号为1、2、……、N。每个人的身高一开始都是不超过1000的正整数
。教主的魔法每次可以把闭区间[L, R](1≤L≤R≤N)内的英雄的身高全部加上一个整数W。(虽然L=R时并不符合
区间的书写规范,但我们可以认为是单独增加第L(R)个英雄的身高)CYZ、光哥和ZJQ等人不信教主的邪,于是他
们有时候会问WD闭区间 [L, R] 内有多少英雄身高大于等于C,以验证教主的魔法是否真的有效。WD巨懒,于是他
把这个回答的任务交给了你。
Input
第1行为两个整数N、Q。Q为问题数与教主的施法数总和。
第2行有N个正整数,第i个数代表第i个英雄的身高。
第3到第Q+2行每行有一个操作:
(1)若第一个字母为"M",则紧接着有三个数字L、R、W。表示对闭区间 [L, R] 内所有英雄的身高加上W。
(2)若第一个字母为"A",则紧接着有三个数字L、R、C。询问闭区间 [L, R] 内有多少英雄的身高大于等于C。
N≤1000000,Q≤3000,1≤W≤1000,1≤C≤1,000,000,000
第2行有N个正整数,第i个数代表第i个英雄的身高。
第3到第Q+2行每行有一个操作:
(1)若第一个字母为"M",则紧接着有三个数字L、R、W。表示对闭区间 [L, R] 内所有英雄的身高加上W。
(2)若第一个字母为"A",则紧接着有三个数字L、R、C。询问闭区间 [L, R] 内有多少英雄的身高大于等于C。
N≤1000000,Q≤3000,1≤W≤1000,1≤C≤1,000,000,000
Output
对每个"A"询问输出一行,仅含一个整数,表示闭区间 [L, R] 内身高大于等于C的英雄数。
Sample Input
5 3
1 2 3 4 5
A 1 5 4
M 3 5 1
A 1 5 4
Sample Output
2
3
【输入输出样例说明】
原先5个英雄身高为1、2、3、4、5,此时[1, 5]间有2个英雄的身高大于等于4。
教主施法后变为1、2、4、5、6,此时[1, 5]间有3个英雄的身高大于等于4。
思路
这是一道分块题,依然是爆模小边块,但是整个分块要用二分,不然会超时;
那么二分就需要每一个分块都是排好序的,这样就要开一个数组,存每个分块内排序后的;
每次改变英雄身高的时候需要,重新建立一次每个分块内排序的数组;
代码:
#include<bits/stdc++.h> using namespace std; typedef long long ll; inline ll read() { ll a=0,f=1; char c=getchar(); while (c<'0'||c>'9') {if (c=='-') f=-1; c=getchar();} while (c>='0'&&c<='9') {a=a*10+c-'0'; c=getchar();} return a*f; } ll n,m,blo; ll a[1000001],v[1000001],sv[1000001],b[1000001]; inline ll L(ll x)//懒人专用函数 { return (x-1)*blo+1; } inline ll R(ll x) { return x*blo; } inline ll dowork(ll x) { for(ll i=L(a[x]);i<=R(a[x]);i++) sv[i]=v[i];//重新构建数组 sort(sv+L(a[x]),sv+R(a[x])+1);//排序 } inline void findout(ll x,ll y,ll z) { if(a[x]==a[y])//如果在一个分块,那么暴模 { for(ll i=x;i<=y;i++) v[i]+=z; dowork(x);//排序 } else { for(ll i=a[x]+1;i<=a[y]-1;i++) b[i]+=z;//大分块要加的,再开一个数组存 for(ll i=x;i<=R(a[x]);i++) v[i]+=z; dowork(x);//重构 for(ll i=L(a[y]);i<=y;i++) v[i]+=z; dowork(y);//重构 } } inline ll reallyans(ll x,ll y,ll z) { ll sum=0; if(a[x]==a[y]) { for(ll i=x;i<=y;i++) if(v[i]+b[a[i]]>=z) sum++;//暴模,没问题吧 return sum; } else { for(ll i=a[x]+1;i<=a[y]-1;i++) { ll l=L(i),r=R(i),ss=0,mid; while(l<=r)//二分查询 { mid=(l+r)>>1; if(sv[mid]+b[i]>=z)//如果中间的数字都大于z r=mid-1, //往左边找是否有满足的 ss=R(i)-mid+1;//因为排了序,所以右边的都满足 else l=mid+1; } sum+=ss; } for(ll i=x;i<=R(a[x]);i++) if(v[i]+b[a[i]]>=z) sum++;//暴力 for(ll i=L(a[y]);i<=y;i++) if(v[i]+b[a[i]]>=z) sum++;//还是暴力 return sum; } } int main() { n=read();m=read();blo=sqrt(n); for(ll i=1;i<=n;i++) { v[i]=read(); a[i]=(i-1)/blo+1;//a数组存每个数在哪个分块 sv[i]=v[i];//建立一个新数组存每个分块排序的 } ll t=n/blo; if(n%blo) t++;//统计分块个数 for(ll i=1;i<=t;i++) sort(sv+L(i),sv+R(i)+1);//排序 char cc[4]; ll ans; for(ll i=1;i<=m;i++) { scanf("%s",cc); ll x=read(),y=read(),z=read(); if(cc[0]=='M') findout(x,y,z); else if(cc[0]=='A') { ans=reallyans(x,y,z); printf("%lld\n",ans); } } return 0; }
有不足的请联系^_^