题目链接:https://loj.ac/problem/6278

题目描述

给出一个长为 \(n\) 的数列,以及 \(n\) 个操作,操作涉及区间加法,询问区间内小于某个值 \(x\) 的元素个数。

输入格式

第一行输入一个数字 \(n\)。

第二行输入 \(n\) 个数字,第 \(i\) 个数字为 \(a_i\),以空格隔开。

接下来输入 \(n\) 行询问,每行输入四个数字 \(opt\)、\(l\)、\(r\)、\(c\),以空格隔开。

若 \(opt=0\),表示将位于 \([l,r]\) 的之间的数字都加 \(c\)。

若 \(opt=1\),表示询问 \([l,r]\) 中,小于 \(c^2\) 的数字的个数。

输出格式

对于每次询问,输出一行一个数字表示答案。

样例输入

4
1 2 2 3
0 1 3 1
1 1 3 2
1 1 4 1
1 2 3 2

样例输出

3
0
2

解题思路

同样还是按照每个块的大小为 \(\lfloor \sqrt{n} \rfloor\) 来进行分块。

这里我们同样用 \(p[i]\) 来表示 \(a[i]\) 所属的分块编号,用 \(v[k]\) 来表示第 \(k\) 个分块的累计更新值。

于此同时,我们再开一个数组 \(b[i]\) ,\(b\) 数组其实就是 \(a\) 数组的一个映射。那么它是怎么映射的呢?

我们假设 \(a[l..r]\) 属于同一个分块,且 \(a[l] 是这个分块的第一个元素,\)a[r$ 是这个分块的最后一个元素,那么 \(b[l..r]\) 就是 \(a[l..r]\) 排好序的结果,即:

  • \(b[l]\) 对应 \(a[l..r]\) 中最小的元素;
  • \(b[l+1]\) 对应 \(a[l..r]\) 中次小的元素;
  • ……
  • \(b[r]\) 对应 \(a[l..r]\) 中最大的元素。

一旦我们修改了某一个分块 \(k\) 中的部分元素,就需要将分块 \(k\) 对应的 \(b\) 数组的这段区间排序(对于分块 \(k\),它对应的坐标范围应该是 \([(k-1) \times m + 1, \min(k \times m, n)]\))。

修改操作:

  • 如果区间没有完整覆盖分块 \(k\),则遍历次分块中的每一个元素,令 \(a[i]+=c\);
  • 否则(完整覆盖分块 \(k\)),则令 \(v[k] += c\)。

查询操作:

  • 如果区间没有完整覆盖分块 \(k\),则遍历次分块中的每一个元素,判断 \(a[i] \lt c^2-v[p[i]]\) 是否成立;
  • 否则(完整覆盖分块 \(k\)),因为 \(b\) 数组具有单调性,对分块 \(k\) 包含的区间范围内的 \(b[l..r]\) 进行二分获取有多少元素 \(b[i] \lt c^2-v[k]\)。

最后将答案汇总。

每次修改的时间复杂度为 \(O( \sqrt{n} )\);

每次查询的时间复杂度为 \(O( \sqrt{n} \times \sqrt{ \sqrt{n} } ) = O(n^{ \frac 34 })\) ,

因为总共有有 \(n\) 次操作,所以整的时间复杂度为 \(O(n \times n^{ \frac 34 })\) 。

实现代码如下:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 50050;
int n, m, a[maxn], b[maxn], p[maxn], v[300], op, l, r, c;
void update_part(int pid) {
int i1 = (pid-1)*m+1, i2 = min(pid*m+1, n+1); // 一定要注意边界条件,我在这里RE了好久,因为最后一个分块长度不一定是m
for (int i = i1; i < i2; i ++)
b[i] = a[i];
sort(b+i1, b+i2);
}
void add(int l, int r, int c) {
if (p[l] == p[r]) { // 说明在同一个分块,直接更新
for (int i = l; i <= r; i ++) a[i] += c;
update_part(p[l]);
return;
}
if (l % m != 1) { // 说明l不是分块p[l]的第一个元素
for (int i = l; p[i]==p[l]; i ++) {
a[i] += c;
}
update_part(p[l]);
}
else v[p[l]] += c;
if (r % m != 0) { // 说明r不是分块p[r]的最后一个元素
for (int i = r; p[i]==p[r]; i --)
a[i] += c;
update_part(p[r]);
}
else v[p[r]] += c;
for (int i = p[l]+1; i < p[r]; i ++)
v[i] += c;
}
int count_part(int pid, int c) {
int i1 = (pid-1)*m+1, i2 = min(pid*m+1, n+1);
int cnt = lower_bound(b+i1, b+i2, c*c-v[pid]) - (b+i1);
return cnt;
}
int get_count(int l, int r, int c) {
int cnt = 0;
if (p[l] == p[r]) { // 说明在同一个分块,直接更新
for (int i = l; i <= r; i ++)
if (a[i]+v[p[i]] < c*c)
cnt ++;
return cnt;
}
if (l % m != 1) { // 说明l不是分块p[l]的第一个元素
for (int i = l; p[i]==p[l]; i ++)
if (a[i]+v[p[i]] < c*c)
cnt ++;
}
else cnt += count_part(p[l], c);
if (r % m != 0) { // 说明r不是分块p[r]的最后一个元素
for (int i = r; p[i]==p[r]; i --)
if (a[i]+v[p[i]] < c*c)
cnt ++;
}
else cnt += count_part(p[r], c);
for (int i = p[l]+1; i < p[r]; i ++)
cnt += count_part(i, c);
return cnt;
}
int main() {
scanf("%d", &n);
m = sqrt(n);
for (int i = 1; i <= n; i ++) p[i] = (i-1)/m + 1;
for (int i = 1; i <= n; i ++) scanf("%d", &a[i]);
for (int i = m; i <= n; i += m) update_part(p[i]); // 初始化所有完整的块
for (int i = 0; i < n; i ++) {
scanf("%d%d%d%d", &op, &l, &r, &c);
if (op == 0) add(l, r, c);
else printf("%d\n", get_count(l, r, c));
}
return 0;
}

最新文章

  1. 通信服务器群集——跨服务器通信Demo(源码)
  2. 多线程之NSOperation和NSOperationQueue
  3. 生成N个不相等的随机数
  4. 最精简的代理设计模式demo - 保姆看孩子
  5. ExpandableListView(可展开的列表组件)的说明以及其用法
  6. JavaWeb基础之tomcat部署
  7. 浏览器对body节点scrollTop解析的差异
  8. OpenStack安装与配置2
  9. GlusterFS缺点分析[转]
  10. 嵌入javascript脚本的位置
  11. PHP的魔法方法
  12. Eclipse快速补全行末分号
  13. windows系统扩展C盘的工具推荐(解决了C盘和压缩卷不相邻无法扩展C盘问题)
  14. C语言第三次作业---单层循环结构
  15. netcore 获取本地网络IP地址
  16. docker环境部署
  17. developer的996,需要谁来拯救
  18. jquery中Get方法请求接口
  19. 数据库 之 E-R设计感想
  20. 10.2.翻译系列:使用Fluent API进行属性映射【EF 6 Code-First】

热门文章

  1. oracle函数 TRIM(c1 from c2)
  2. oracle函数 dbtimezone
  3. 学习layer弹层组件移动版
  4. Android教程 -09 数据的持久化存储
  5. Springboot2 Logback mybatis 打印sql执行语句
  6. laravel博客后台操作步骤
  7. artTemplate模版引擎的使用
  8. &lt;STL源码剖析&gt; 6.3.6 power
  9. Scrap简介
  10. python基础十五之递归函数