P3130 题解
Melo.VVちゃん · · 题解
索引:分块是一种优美的暴力。
分块为什么优美
简述分块思想:将原序列划分成一堆块,大块整体操作,小块暴力更改。
分块的优美之处:我们尝试将原序列划分成
整体复杂度分析:我们不妨令
为什么要用分块:好想,实现简单(总比懒标记好记),直观。
题目实现
首先最为朴素的区间加和求和操作,我们对于小块直接暴力更改、求和,对于大块,考虑维护一个增量标记
对于稍微不朴素的区间最小值操作,显然如果我们对整块进行区间加,那么这个块的最小值也相应地增加这个数,所以可以在
千万别忘了
其余的看代码:
#include <bits/stdc++.h>
#define inf LONG_LONG_MAX
#define f(i ,m ,n ,x) for (int i = (m) ;i <= (n) ;i += (x))
using namespace std ;
template < typename T > inline void read ( T&x ) {
x = 0 ;
bool flag (0) ;
register char ch = getchar () ;
while (! isdigit (ch)) {
flag = ch == '-' ;
ch = getchar () ;
}
while (isdigit (ch)) {
x = (x << 1) + (x << 3) + (ch ^ 48) ;
ch = getchar () ;
}
flag ? x = -x : 0 ;
}
const int N = 2e5 + 7 ;
const int M (666) ;
#define int long long
int n ,q ,l[M] ,r[M] ,belong[N] ;
int a[N] ,sum[M] ,add[M] ,minn[M] ;
namespace shin {
inline void modify (int L ,int R ,int k) {
int p = belong[L] ,q = belong[R] ;
if (p == q) {
minn[p] = inf ; // 直接重新更新
f (i ,l[p] ,r[p] ,1) { // L、R 在一个块内
if (i >= L && i <= R) {
a[i] += k ; // 区间加
}
minn[p] = min (minn[p] ,a[i] + add[p]) ;
}
sum[p] += (R - L + 1) * k ;
return ;
}
// 若不在,则可以变成若干大块加两小块的样式
minn[p] = minn[q] = inf ;
f (i ,l[p] ,r[p] ,1) {
if (i >= L) {
a[i] += k ;
}
minn[p] = min (minn[p] ,a[i] + add[p]) ;
}
sum[p] += (r[p] - L + 1) * k ;
f (i ,l[q] ,r[q] ,1) {
if (i <= R) {
a[i] += k ;
}
minn[q] = min (minn[q] ,a[i] + add[q]) ;
}
sum[q] += (R - l[q] + 1) * k ;
f (i ,p + 1 ,q - 1 ,1) {
add[i] += k ;
minn[i] += k ;
}
}
inline int find (int L ,int R) {
int p = belong[L] ,q = belong[R] ;
if (p == q) {
int ans = inf ;
f (i ,L ,R ,1) {
ans = min (ans ,a[i] + add[p]) ;
}
return ans ;
}
int ans = inf ;
f (i ,L ,r[p] ,1) {
ans = min (ans ,a[i] + add[p]) ;
}
f (i ,l[q] ,R ,1) {
ans = min (ans ,a[i] + add[q]) ;
}
f (i ,p + 1 ,q - 1 ,1) {
ans = min (ans ,minn[i]) ;
}
return ans ;
}
inline int query (int L ,int R) {
int p = belong[L] ,q = belong[R] ;
if (p == q) {
int ans (0) ;
f (i ,L ,R ,1) {
ans += a[i] + add[p] ;
}
return ans ;
}
int ans (0) ;
f (i ,L ,r[p] ,1) {
ans += a[i] + add[p] ;
}
f (i ,l[q] ,R ,1) {
ans += a[i] + add[q] ;
}
f (i ,p + 1 ,q - 1 ,1) {
ans += sum[i] + add[i] * (r[i] - l[i] + 1) ;
}
return ans ;
}
}
signed main () {
read (n) ,read (q) ;
f (i ,1 ,n ,1) {
read (a[i]) ;
}
int t = sqrt (n) ; // 求块长和块数
f (i ,1 ,t ,1) {
minn[i] = inf ;
l[i] = (i - 1) * t + 1 ;
r[i] = i * t ;
} // 记录大块左右断点
if (r[t] < n) {
t ++ ;
l[t] = r[t - 1] + 1 ;
r[t] = n ;
} // t 很可能不是真正的 sqrt (n),也就是说最后可能会剩下一个小块
f (i ,1 ,t ,1) {
f (j ,l[i] ,r[i] ,1) {
belong[j] = i ;
sum[i] += a[j] ;
minn[i] = min (minn[i] ,a[j]) ;
}
} // 记录每个点属于哪个块,顺带统计块和和块内最小值
do {
int a ,b ;
int c ;
char ch ;
cin >> ch ;
if (ch == 'P') {
read (a) ,read (b) ,read (c) ;
shin :: modify (a ,b ,c) ;
}
else if (ch == 'M') {
read (a) ,read (b) ;
cout << shin :: find (a ,b) << '\n' ;
}
else {
read (a) ,read (b) ;
cout << shin :: query (a ,b) << '\n' ;
}
} while (q -- != 1) ;
return 0 ;
}