题解(教主的魔法)P2801
题目
教主的魔法
题目描述
教主最近学会了一种神奇的魔法,能够使人长高。于是他准备演示给 XMYZ 信息组每个英雄看。于是 \(N\) 个英雄们又一次聚集在了一起,这次他们排成了一列,被编号为 \(1, 2, \ldots, N\)。
每个人的身高一开始都是不超过 \(1000\) 的正整数。教主的魔法每次可以把闭区间 \([L, R]\)(\(1≤L≤R≤N\))内的英雄的身高全部加上一个整数 \(W\)。(虽然 \(L=R\) 时并不符合区间的书写规范,但我们可以认为是单独增加第 \(L(R)\) 个英雄的身高)
CYZ、光哥和 ZJQ 等人不信教主的邪,于是他们有时候会问 WD 闭区间 \([L, R]\) 内有多少英雄身高大于等于 \(C\),以验证教主的魔法是否真的有效。
WD 巨懒,于是他把这个回答的任务交给了你。
输入格式
第 \(1\) 行为两个整数 \(N, Q\)。\(Q\) 为问题数与教主的施法数总和。
第 \(2\) 行有 \(N\) 个正整数,第 \(i\) 个数代表第 \(i\) 个英雄的身高。
第 \(3\) 到第 \(Q+2\) 行每行有一个操作:
-
若第一个字母为
M,则紧接着有三个数字 \(L, R, W\)。表示对闭区间 \([L, R]\) 内所有英雄的身高加上 \(W\)。 -
若第一个字母为
A,则紧接着有三个数字 \(L, R, C\)。询问闭区间 \([L, R]\) 内有多少英雄的身高大于等于 \(C\)。
输出格式
对每个 A 询问输出一行,仅含一个整数,表示闭区间 \([L, R]\) 内身高大于等于 \(C\) 的英雄数。
样例 #1
样例输入 #1
5 3
1 2 3 4 5
A 1 5 4
M 3 5 1
A 1 5 4
样例输出 #1
2
3
提示
【输入输出样例说明】
原先 \(5\) 个英雄身高为 \(1, 2, 3, 4, 5\),此时 \([1, 5]\) 间有 \(2\) 个英雄的身高大于等于 \(4\)。教主施法后变为 \(1, 2, 4, 5, 6\),此时 \([1, 5]\) 间有 \(3\) 个英雄的身高大于等于 \(4\)。
【数据范围】
对于 \(30\%\) 的数据,\(N≤1000\),\(Q≤1000\)。
对于 \(100\%\) 的数据,\(N≤10^6\),\(Q≤3000\),\(1≤W≤1000\),\(1≤C≤10^9\)。
\(\text{upd 2022.8.18}\):新增加一组 Hack 数据。
打个暴力先
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+10;
inline int read(){
int f(1),x(0);
char ch=getchar();
for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-1;
for(;isdigit(ch);ch=getchar()) x=(x<<1)+(x<<3)+(ch^48);
return f*x;
}
inline void write(int x){
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
}
int n,m,a[N],t,L[N],R[N];
main(void){
n=read(),m=read();
for(register int i=1;i<=n;i++){
a[i]=read();
}
while(m--){
char ch=getchar();getchar();
int l=read(),r=read(),v=read();
if(ch=='M'){
for(register int i=l;i<=r;i++){
a[i]+=v;
}
}
else{
int arc(0);
for(register int i=l;i<=r;i++){
if(a[i]>=v){
arc++;
}
}
write(arc),putchar('\n');
}
}
return 0;
}
OJ得66分,luogu得0分 
然后怎么分块呢
预处理
打个板子捏
记录每个块的左右边界和\(pos[i]\)记录对应位置
n=read(),m=read();
t=sqrt(n);
for(register int i=1;i<=n;i++){
a[i]=read();
}
for(register int i=1;i<=t;i++){
L[i]=(i-1)*t+1;
R[i]=i*t;
}
if(R[t]<n){
t++,L[t]=R[t-1]+1,R[t]=n;
}
for(register int i=1;i<=t;i++){
for(register int j=L[i];j<=R[i];j++){
pos[j]=i;
}
}
查询
对于查询的时候因为不能对中间一块一个一个一个得遍历找>=v の数有多少
所以可以给这个块内的数组排个序(满足单调),用二分查找出初始点,然后用右区间减就行
例如一个块内元素有
5 2 3 4 1
我们将他升序排列
1 2 3 4 5
L[i] R[i]
例如找>=2的数
二分找到2是 \(L[i]+1\) 然后用\(R[i]-(L[i]+1)+1\)
即\(R[i]-\)二分答案\(+1\)
对于不足整块即左右两边的数组可以用暴力搜索
对于\(pos[l]=pos[r]\)的部分特判暴力搜索就行
inline static int testify(int l,int r,int v){
int arc(0),p=pos[l],q=pos[r];
if(p==q){
for(register int i=l;i<=r;i++){
if(add[p]+a[i]>=v) arc++;
}
return arc;
}
else{
for(register int i=l;i<=R[p];i++){
if(add[p]+a[i]>=v) arc++;
}
for(register int i=L[q];i<=r;i++){
if(add[q]+a[i]>=v) arc++;
}
for(register int i=p+1;i<=q-1;i++){
int ll=L[i],rr=R[i]+1,mid(0);
while(ll<rr){
mid=(ll+rr)>>1;
if(d[mid]+add[i]<v){
ll=mid+1;
}
else{
rr=mid;
}
}
arc+=(R[i]+1)-ll;
}
}
return arc;
}
修改
板子但是加入一个新数组\(d[i]\)对于原来\(a[i]\)的块进行\(sort\)升序排列
对于中间块用\(add[i]\)进行标记
inline void SORT(int now){
for(register int i=L[now];i<=R[now];i++){
d[i]=a[i];
}
sort(d+L[now],d+R[now]+1);
}
inline static void change(int l,int r,int v){
int p=pos[l],q=pos[r];
if(p==q){
for(register int i=l;i<=r;i++){
a[i]+=v;
}
SORT(p);
}
else{
for(register int i=l;i<=R[p];i++){
a[i]+=v;
}
SORT(p);
for(register int i=L[q];i<=r;i++){
a[i]+=v;
}
SORT(q);
for(register int i=p+1;i<=q-1;i++){
add[i]+=v;
}
}
}
完结撒花,附上\(AC\)代码
注意\(d[i]\)重排列数组要在 \(main\)函数 初始化一下捏
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1000114;
inline int read(){
int f(1),x(0);
char ch=getchar();
for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-1;
for(;isdigit(ch);ch=getchar()) x=(x<<1)+(x<<3)+(ch^48);
return f*x;
}
inline void write(int x){
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
}
int n,m,a[N],t,L[N],R[N],d[N],pos[N],add[N];
inline void SORT(int now){
for(register int i=L[now];i<=R[now];i++){
d[i]=a[i];
}
sort(d+L[now],d+R[now]+1);//d+L[now]是对应块の左端点,
//数组元素个数为R[now]-L[now]+1
//所以d+L[now]+(R[now]-L[now]+1)=d+R[now]+1是sort的右边
}
inline static void change(int l,int r,int v){
int p=pos[l],q=pos[r];
if(p==q){
for(register int i=l;i<=r;i++){
a[i]+=v;
}
SORT(p);
}
else{
for(register int i=l;i<=R[p];i++){
a[i]+=v;
}
SORT(p);
for(register int i=L[q];i<=r;i++){
a[i]+=v;
}
SORT(q);
for(register int i=p+1;i<=q-1;i++){
add[i]+=v;
}
}
}
inline static int testify(int l,int r,int v){
int arc(0),p=pos[l],q=pos[r];
if(p==q){
for(register int i=l;i<=r;i++){
if(add[p]+a[i]>=v) arc++;
}
return arc;
}
else{
for(register int i=l;i<=R[p];i++){
if(add[p]+a[i]>=v) arc++;
}
for(register int i=L[q];i<=r;i++){
if(add[q]+a[i]>=v) arc++;
}
for(register int i=p+1;i<=q-1;i++){
int ll=L[i],rr=R[i]+1,mid(0);
while(ll<rr){
mid=(ll+rr)>>1;
if(d[mid]+add[i]<v){
ll=mid+1;
}
else{
rr=mid;
}
}
arc+=(R[i]+1)-ll;
}
}
return arc;
}
main(void){
n=read(),m=read();
t=sqrt(n);
for(register int i=1;i<=n;i++){
a[i]=read();
}
for(register int i=1;i<=t;i++){
L[i]=(i-1)*t+1;
R[i]=i*t;
}
if(R[t]<n){
t++,L[t]=R[t-1]+1,R[t]=n;
}
for(register int i=1;i<=t;i++){
for(register int j=L[i];j<=R[i];j++){
pos[j]=i;
}
}
for(register int i=1;i<=t;i++){
SORT(i);
}
while(m--){
char ch;
cin>>ch;
int l=read(),r=read(),v=read();
if(ch=='M'){
change(l,r,v);
}
else if(ch=='A'){
write(testify(l,r,v)),putchar('\n');
}
}
return 0;
}
闲话
你这还不算什么,上次在学校食堂吃饭,突然看到饭里有一团红色????,我以为是鞭炮,准备吓一吓同学,好家伙,原来是核弹????,因为我是c语言大佬,所以我直接用程序黑了核弹程序????,后来全校都知道了,校长都吓的给我磕头????,我也没那么强吧,也就180开捷豹抽中华????,上次我在公交车上玩原神 m,有人叫我op,我当时就把他打了一顿????,他顺手拿起一根20cm粗的钢管打我,然后我把钢管折断了,把他打得青一块紫一块的,让他住了好几天院????????,后来他出院了,给我赔礼道歉,请我给了杯白水,我一喝,好家伙,原来是硫酸????,那钻心的疼让我一生难忘,还好我是体育生,跑得快 m,硫酸都被我甩出来了????,腿脚还利落,只是胃没了,在医院,医生测量了下我的体温,好家伙一百多度????,本来是不疼的,结果庸医误诊说修养就好了,我疼得受不了了????,就自己拿502胶胶了一下,一会就没事了????,好家伙502胶里有核弹,当时瞬间爆炸把我都炸到M87星云了????,幸亏我是体育生,没什么感觉,就擦破了点皮????,跑了差不多几个月吧,才回到地球,刚回到地球就看到地上有一块雷酸汞,我以为是薄荷糖,就拿来吃,当时嘴巴里突然一声巨响????????,据说那是雷酸汞爆炸的声音,当时我就只记得两眼一黑,昏过去了????,据说大脑小脑都炸飞了,还好我是体育生,复活赛只打了一个月就复活了????,还顺便学会了影分身十字斩结果释放了一地球的陈睿和孙笑川????,幸好我是体育生,玩了几天原农粥就没事了????,后来陈睿还送了我个自动变形劲爆火辣小火车,好像还是必胜客原神里的初音未来联名的,是限定的,已经绝版了????,现在想起来我一直在疑惑为什么没讹陈睿几十万????

浙公网安备 33010602011771号