洛谷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\)。
简单分析下题目,大概就是一群相信科学的people想判断教主的魔法科不科学,在施法后查下身高。但自己又巨懒,于是你喜提一道oi题,所以,正解应该是让他们自己去量自己的身高。先看数据量,有点小多这英雄都这么普遍的吗?,所以我们选择分块。
正片开始
首先,很明显这是一道分块的板子题,所以我们直接考虑分块做法(绝不是我不会写其他的)。
1.创建分块
对于每个分块,我们需要维护分块大小(block), 分块数量(tot), 每一块的左右端点(l,r), 以及每一个点(i)的归属块(bel[i]), 由于题目的需要需要一个d数组copy一下进行排序处理。
一般来说,分块的大小为\(\sqrt{n}\) , 所以我们这里的block=\(\sqrt{n}\)(绝不是难的我不会),此时我们很容易得到块的数量为tot=n/block,当有剩余散块时,块数还应+1。
同样,块的左右边界也可以推理出来
\(L_{x}=(x-1)\cdot block+1\),\(R_{x}=x\cdot block\),另外\(R_{tot}=n\)。
code:
void build()
{
block=sqrt(n);tot=n/block;
if(n%block) tot++;
for(int i=1;i<=tot;i++)
{
l[i]=(i-1)*block+1;
r[i]=i*block;
}
r[tot]=n;
for(int i=1;i<=n;i++) bel[i]=(i-1)/block+1,d[i]=a[i];
for(int i=1;i<=tot;i++) sort(d+l[i],d+r[i]+1);
}
2.处理修改操作
-
首先我们处理区间[l,r]完全在一个块里的情况,此时直接暴力枚举加值即可。
-
考虑区间跨度超过1个块以上的情况,利用线段树的小技巧,我们可以很快的想到在整块区间打标记的做法。对于散块直接暴力枚举即可。
-
由于我们还需要要快速的求出答案,所以需要维护出一个有序的数组d。
code:
void change(int x,int y,int k)
{
if(bel[x]==bel[y])
{
for(int i=x;i<=y;i++) a[i]+=k;
for(int i=l[bel[x]];i<=r[bel[x]];i++) d[i]=a[i];
sort(d+l[bel[x]],d+r[bel[x]]+1);
}
else
{
for(int i=x;i<=r[bel[x]];i++) a[i]+=k;
for(int i=l[bel[x]];i<=r[bel[x]];i++) d[i]=a[i];
sort(d+l[bel[x]],d+r[bel[x]]+1);
for(int i=l[bel[y]];i<=y;i++) a[i]+=k;
for(int i=l[bel[y]];i<=r[bel[y]];i++) d[i]=a[i];
sort(d+l[bel[y]],d+r[bel[y]]+1);
for(int i=bel[x]+1;i<=bel[y]-1;i++) lazy[i]+=k;
}
}
3.处理查询操作
又到最美好的查答案阶段了,在这距离AC不远的地方,我们选择和修改一样的思路进行处理。
- 查询区间[l,r]在一个块里,直接暴戾求解答案,
反正就\(\sqrt{n}\) 个数,简单分析可以发现复杂度完全可以接受。 - 对于多于一个块的区间,也是分成散块和整块,暴力求散,二分求整。
code:
void query(int x,int y,int k)
{
int ans=0;
if(bel[x]==bel[y])
{
for(int i=x;i<=y;i++) if(lazy[bel[x]]+a[i]>=k) ans++;
cout<<ans<<endl;
return;
}
else
{
for(int i=x;i<=r[bel[x]];i++) if(lazy[bel[x]]+a[i]>=k) ans++;
for(int i=l[bel[y]];i<=y;i++) if(lazy[bel[y]]+a[i]>=k) ans++;
for(int i=bel[x]+1;i<=bel[y]-1;i++)
{
int ll=l[i],rr=r[i],res=0,mid;
while(ll<=rr)
{
mid=(ll+rr)>>1;
if(d[mid]+lazy[i]>=k) rr=mid-1,res=r[i]-mid+1;
else ll=mid+1;
}
ans+=res;
}
cout<<ans<<endl;
return;
}
}
主体到此就结束了。
完整代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n,q,d[N],a[N],l[N],r[N],bel[N],lazy[N],ans;
int block,tot,x,y,k;
void build()
{
block=sqrt(n);tot=n/block;
if(n%block) tot++;
for(int i=1;i<=tot;i++)
{
l[i]=(i-1)*block+1;
r[i]=i*block;
}
r[tot]=n;
for(int i=1;i<=n;i++) bel[i]=(i-1)/block+1,d[i]=a[i];
for(int i=1;i<=tot;i++) sort(d+l[i],d+r[i]+1);
}
void change(int x,int y,int k)
{
if(bel[x]==bel[y])
{
for(int i=x;i<=y;i++) a[i]+=k;
for(int i=l[bel[x]];i<=r[bel[x]];i++) d[i]=a[i];
sort(d+l[bel[x]],d+r[bel[x]]+1);
}
else
{
for(int i=x;i<=r[bel[x]];i++) a[i]+=k;
for(int i=l[bel[x]];i<=r[bel[x]];i++) d[i]=a[i];
sort(d+l[bel[x]],d+r[bel[x]]+1);
for(int i=l[bel[y]];i<=y;i++) a[i]+=k;
for(int i=l[bel[y]];i<=r[bel[y]];i++) d[i]=a[i];
sort(d+l[bel[y]],d+r[bel[y]]+1);
for(int i=bel[x]+1;i<=bel[y]-1;i++) lazy[i]+=k;
}
}
void query(int x,int y,int k)
{
int ans=0;
if(bel[x]==bel[y])
{
for(int i=x;i<=y;i++) if(lazy[bel[x]]+a[i]>=k) ans++;
cout<<ans<<endl;
return;
}
else
{
for(int i=x;i<=r[bel[x]];i++) if(lazy[bel[x]]+a[i]>=k) ans++;
for(int i=l[bel[y]];i<=y;i++) if(lazy[bel[y]]+a[i]>=k) ans++;
for(int i=bel[x]+1;i<=bel[y]-1;i++)
{
int ll=l[i],rr=r[i],res=0,mid;
while(ll<=rr)
{
mid=(ll+rr)>>1;
if(d[mid]+lazy[i]>=k) rr=mid-1,res=r[i]-mid+1;
else ll=mid+1;
}
ans+=res;
}
cout<<ans<<endl;
return;
}
}
int main()
{
cin>>n>>q;
for(int i=1;i<=n;i++) cin>>a[i];
build();
while(q--)
{
char op;cin>>op;
int x,y,z;cin>>x>>y>>z;
if(op=='M')change(x,y,z);
else query(x,y,z);
}
return 0;
}
code完成