P2801 教主的魔法 题解

读题

首先,形式化的题意:

有一个 N N N 个数的数列 a a a,存在两种操作:

  1. 将区间 [ l , r ] [l,r] [l,r] 中所有的数加上 w w w
  2. 统计区间 [ l , r ] [l,r] [l,r] 中大于 c c c 的数的个数。

可以使用分块算法求解。

分块

核心思想:将长度为 n n n 的数组分成 ceil( n k ) \text{ceil(}\frac n k\text{)} ceil(kn) 块,每块长度为 k k k

那么我们将分块思想运用在题目中:

思路

  • 对于操作1:分两种情况
      1. l l l r r r 在同一块中时,暴力修改即可。最坏时间复杂度为 O ( k ) \text{O}(k) O(k)
      1. l l l r r r 不在同一块中,此时对于 l l l r r r 所在块暴力修改。其余块使用 delta \text{delta} delta 标记,统一加上 w w w。时间复杂度亦为 O ( k ) \text{O}(k) O(k)
  • 对于操作二同理:
    • l l l r r r 在同一块中时,暴力查询即可。最坏时间复杂度为 O ( n k ) \text{O}(\frac{n}{k}) O(kn)
    • l l l r r r 不在同一块中,此时对于 l l l r r r 所在块暴力查询。其余块排序,使用 lower_bound 统计。时间复杂度为 O ( n k log ⁡ k ) \text{O}(\frac{n}{k}\log k) O(knlogk)

OI-wiki说:利用均值不等式可知,当
n k = k \dfrac{n}{k}=k kn=k ,即 k = n k=\sqrt n k=n 时,单次操作的时间复杂度最优。

所以,总时间复杂度为 O ( max ⁡ ( n , n log ⁡ n ) ) \text{O}(\max(n,\sqrt n\log \sqrt n)) O(max(n,n logn ))

AC   code \texttt{AC code} AC code

#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
const int N=1000005;
int n,q,cnt;
int a[N];
int t[N];
int st[N],ed[N],siz[N],belong[N];
int delta[N],sum[N];
void Sort(int blockId) {
	for(int i=st[blockId];i<=ed[blockId];i++)
		t[i]=a[i];
	sort(t+st[blockId],t+ed[blockId]+1);
}
int Query(int l,int r,int c) {
	int ans=0;
	if(belong[l]==belong[r]) {
		for(int i=l;i<=r;i++) {
			ans+=(a[i]+delta[belong[i]]>=c);
		}
		return ans;
	}
	for(int i=l;i<=ed[belong[l]];i++) 
		ans+=(a[i]+delta[belong[l]]>=c);
	//printf("s1:%d\n",ans);
	for(int i=belong[l]+1;i<=belong[r]-1;i++)
		ans+=ed[i]-(lower_bound(t+st[i],t+ed[i]+1,c-delta[i])-t)+1;
	//printf("s2:%d\n",ans);
	for(int i=st[belong[r]];i<=r;i++)
		ans+=(a[i]+delta[belong[r]]>=c);
	//printf("s3:%d\n",ans);
	return ans;
}
void Modify(int l,int r,int w) {
	if(belong[l]==belong[r]) {
		for(int i=l;i<=r;i++) a[i]+=w;
		Sort(belong[l]);
		return ;
	}
	for(int i=l;i<=ed[belong[l]];i++) a[i]+=w;
	for(int i=st[belong[r]];i<=r;i++) a[i]+=w;
	for(int i=belong[l]+1;i<=belong[r]-1;i++) delta[i]+=w;
	Sort(belong[l]);
	Sort(belong[r]);
}
int main() {
	cin>>n>>q;
	for(int i=1;i<=n;i++) {
		cin>>a[i];
		t[i]=a[i];
	}
	cnt=sqrt(n);
	for(int i=1;i<=cnt;i++) {
		st[i]=n/cnt*(i-1)+1;
		ed[i]=n/cnt*i;
	}
	ed[cnt]=n;
	for(int i=1;i<=cnt;i++) {
		for(int j=st[i];j<=ed[i];j++)
			belong[j]=i;
		siz[i]=ed[i]-st[i]+1;
		Sort(i);
	}
	char op;
	int l,r,c;
	for(int i=1;i<=q;i++) {
		cin>>op>>l>>r>>c;
		switch(op) {
		case 'A':
			printf("%d\n",Query(l,r,c));
			break;
		case 'M':
			Modify(l,r,c);
			break;
		}
	}
    return 0;
}

Tips:

belong i \text{belong}_i belongi i i i 属于的块编号。

st i , ed i , siz i \text{st}_i,\text{ed}_i, \text{siz}_i sti,edi,sizi i i i 所在的块的起点,终点,大小。

a i \text{a}_i ai 原数组。

t i \text t_i ti 排序后数组。

  • 19
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值