分块——以poj3468和洛谷P4168为例

2024-01-31 01:58

本文主要是介绍分块——以poj3468和洛谷P4168为例,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

当我们遇到简单的区间问题时,一般会想到树状数组或者线段树。但是当区间信息不符合区间可加可减性时,我们就要另寻他路了。这时我们可以选择用分块来解决问题。

分块要将你要处理的区间划分成长度大致相等的几块。为什么是大致相等呢?因为最后一个剩下的区间长度是难以控制的,剩下来的长度我们就让它自己成为一个区间,大概是这个样子。在这里插入图片描述
对于分块来说,我们要先记录下每个区间的左右极限,以及每个点所在的区间,比如点 1 1 1在第一个区间,点 5 5 5在第二个区间,之后在进行区间处理的时候,访问它们所在的区间。对于刚开始的信息还要进行预处理。

  • 如果它们在一个区间内,例如点 1 1 1和点 3 3 3,那我们直接去遍历点 1 1 1到点 3 3 3即可
  • 如果它们之间隔了几个区间,例如点 2 2 2和点 7 7 7,那么对于点 2 2 2和点 7 7 7所在的区间,我们按照上一条的方法,遍历点 2 2 2到点 2 2 2所在区间的右极限,遍历点 7 7 7所在区间的左极限到点 7 7 7。对于它们之间的区间,我们就对整个区间进行操作。

我们以poj3468为例。这是一道很常规的线段树区间更新,区间求和的板子题。我们将区间以 n \sqrt{n} n 为长度划分,用分块写出来大概是这样的。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;typedef long long ll;
const int maxn = 1e5 + 7;int l[maxn], r[maxn], pos[maxn];
ll sum[maxn], a[maxn], c, lazy[maxn];
int n, m, t, ql, qr;
char op[1];
void add(int ql, int qr, ll c) {int pl = pos[ql], pr = pos[qr];if (pl == pr) {for (int i = ql; i <= qr; ++i) {a[i] += c;}sum[pl] += c * (qr - ql + 1);return;}for (int i = ql; i <= r[pl]; ++i) {a[i] += c;}sum[pl] += c * (r[pl] - ql + 1);for (int i = l[pr]; i <= qr; ++i) {a[i] += c;}sum[pr] += c * (qr - l[pr] + 1);for (int i = pl + 1; i < pr; ++i) {lazy[i] += c;}
}ll query(int ql, int qr) {int pl = pos[ql], pr = pos[qr];ll res = 0;//应注意对于边边角角的点也要加上lazy值if (pl == pr) {for (int i = ql; i <= qr; ++i) {res += a[i] + lazy[pl];}return res;}for (int i = ql; i <= r[pl]; ++i) res += a[i] + lazy[pl];for (int i = l[pr]; i <= qr; ++i) res += a[i] + lazy[pr];for (int i = pl + 1; i < pr; ++i) res += sum[i] + lazy[i] * (r[i] - l[i] + 1);return res;
}
int main() {scanf("%d %d", &n, &m);for (int i = 1; i <= n; ++i) scanf("%lld", &a[i]);int t = sqrt(n);for (int i = 1; i <= t; ++i) {l[i] = (i - 1) * t + 1;r[i] = i * t;}if (n > t * t) {l[t + 1] = t * t + 1;r[t + 1] = n;t++;}for (int i = 1; i <= t; ++i) {for (int j = l[i]; j <= r[i]; ++j) {pos[j] = i;sum[i] += a[j];}}while (m--) {scanf("%s %d %d", op, &ql, &qr);if (op[0] == 'Q') {printf("%lld\n", query(ql, qr));}else {scanf("%lld", &c);add(ql, qr, c);}}return 0;
}

到这里可能还会觉得分块没有什么用,在代码量和时间复杂度上都不如别的数据结构。但是如果我们遇到了洛谷P4168这类型的题目,就无法简单地用线段树和树状数组去处理。

它要求输出区间出现次数最多的数,如果有多个这样的数,则输出最小的,并且强制在线。区间众数不满足区间相加,所以我们采用分块来写。我们现在还不能确定分块要分多少块,这里先设为 T T T。由于这道题目的数值很大,我们采取离散化处理。离散化之后,我们先确定好各点所在的区间,然后预处理 n u m [ i ] [ j ] num[i][j] num[i][j],代表某个区间到某个区间的编号是 i i i,其中第 j j j个颜色出现了多少次,并用 r e s res res数组来记录当前区间的答案,总共有 T 2 T^2 T2个区间。这样预处理之后,我们在进行访问的时候,就可以直接得到中间一段块的信息,就只用处理边边角角的数据。同时应该注意到,若访问的是处于同一区间的点,或者是相邻区间的点,都要进行遍历。

然后我们发现预处理需要 O ( T 2 N ) O(T^2N) O(T2N),分块的长度是 N / T N/T N/T,所以我们 M M M次询问需要 O ( M N / T ) O(MN/T) O(MN/T)。我们令 T 2 N = M N / T T^2N = MN/T T2N=MN/T M M M N N N为同一个数量级,可得 T T T N 3 \sqrt[3]{N} 3N 。所以确定了 T T T的大小。

#include <bits/stdc++.h>
using namespace std;typedef long long ll;
const int maxn = 4e4 + 7;int num[1000][maxn];
int l[maxn], r[maxn], pos[maxn], res[1000], ma[1000];
int cor[maxn], a[maxn], cnt[1000][maxn], c[maxn];
int n, m, t, ql, qr, len, tot;
int query(int ql, int qr) {int ans, maxx = 0;int pl = pos[ql], pr = pos[qr];if (pr - pl <= 1) {for (int i = ql; i <= qr; ++i) {c[cor[i]]++;if (c[cor[i]] > maxx) maxx = c[cor[i]], ans = cor[i];else if (c[cor[i]] == maxx && cor[i] < ans) ans = cor[i];}for (int i = ql; i <= qr; ++i) c[cor[i]]--;return ans;}ans = res[cnt[pl + 1][pr - 1]];maxx = ma[cnt[pl + 1][pr - 1]];for (int i = ql; i <= r[pl]; ++i) {c[cor[i]]++;if (c[cor[i]] + num[cnt[pl + 1][pr - 1]][cor[i]] > maxx) {maxx = c[cor[i]] + num[cnt[pl + 1][pr - 1]][cor[i]];ans = cor[i];}else if (c[cor[i]] + num[cnt[pl + 1][pr - 1]][cor[i]] == maxx) {if (cor[i] < ans) ans = cor[i];}}for (int i = l[pr]; i <= qr; ++i) {c[cor[i]]++;if (c[cor[i]] + num[cnt[pl + 1][pr - 1]][cor[i]] > maxx) {maxx = c[cor[i]] + num[cnt[pl + 1][pr - 1]][cor[i]];ans = cor[i];}else if (c[cor[i]] + num[cnt[pl + 1][pr - 1]][cor[i]] == maxx) {if (cor[i] < ans) ans = cor[i];}}for (int i = ql; i <= r[pl]; ++i) c[cor[i]]--;for (int i = l[pr]; i <= qr; ++i) c[cor[i]]--;return ans;
}
int main() {scanf("%d %d", &n, &m);t = pow(n, 1.0 / 3);len = n / t;for (int i = 1; i <= n; ++i)  scanf("%d", &cor[i]), a[i] = cor[i];for (int i = 1; i <= t; ++i) {l[i] = r[i - 1] + 1;r[i] = i * len;}if (n > r[t]) {t++;l[t] = r[t - 1] + 1;r[t] = n;}for (int i = 1; i <= n; ++i) {for (int j = l[i]; j <= r[i]; ++j) pos[j] = i;}sort(a + 1, a + 1 + n);tot = unique(a + 1, a + 1 + n) - a - 1;for (int i = 1; i <= n; ++i) {cor[i] = lower_bound(a + 1, a + 1 + tot, cor[i]) - a;}int now = 0;for (int i = 1; i <= t; ++i) {int L = l[i];for (int j = i; j <= t; ++j) {int R = r[j];cnt[i][j] = ++now;int maxx = 0;for (int k = L; k <= R; ++k) {num[cnt[i][j]][cor[k]]++;if (num[cnt[i][j]][cor[k]] > maxx) {maxx = num[cnt[i][j]][cor[k]];res[cnt[i][j]] = cor[k];ma[cnt[i][j]] = maxx;}else if (num[cnt[i][j]][cor[k]] == maxx && cor[k] < res[cnt[i][j]]) res[cnt[i][j]] = cor[k];}}}int pre = 0;while (m--) {scanf("%d %d", &ql, &qr);ql = ((ql + pre - 1) % n) + 1, qr = ((qr + pre - 1) % n) + 1;if (ql > qr) swap(ql, qr);pre = a[query(ql, qr)];printf("%d\n", pre);}return 0;
}

这篇关于分块——以poj3468和洛谷P4168为例的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/662242

相关文章

poj3468(线段树成段更新模板题)

题意:包括两个操作:1、将[a.b]上的数字加上v;2、查询区间[a,b]上的和 下面的介绍是下解题思路: 首先介绍  lazy-tag思想:用一个变量记录每一个线段树节点的变化值,当这部分线段的一致性被破坏我们就将这个变化值传递给子区间,大大增加了线段树的效率。 比如现在需要对[a,b]区间值进行加c操作,那么就从根节点[1,n]开始调用update函数进行操作,如果刚好执行到一个子节点,

高精度计算(代码加解析,洛谷p1601,p1303)除法待更新

目录 高精度加法 高精度减法 高精度乘法 高精度加法 我们知道在c++语言中任何数据类型都有一定的表示范围。当两个被加数很大时,正常加法不能得到精确解。在小学,我们做加法都采用竖式方法。那么我们也只需要按照加法进位的方式就能得到最终解。 8 5 6+ 2 5 5-------1 1 1 1 加法进位: c[i] = a[i] + b[i];if(c[i] >=

洛谷 凸多边形划分

T282062 凸多边形的划分 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 先整一个半成品,高精度过两天复习一下补上 #include <iostream>#include <algorithm>#include <set>#include <cstring>#include <string>#include <vector>#include <map>

Matlab/Simulink和AMEsim联合仿真(以PSO-PID算法为例)

目录 安装软件和配置环境变量 Matlab/Simulink和AMEsim联合仿真详细流程 非常重要的一点 Simulink模型和AMEsim模型用S-Function建立连接 从AMEsim软件打开Matlab Matlab里的设置 Matlab的.m文件修改(对于PSO-PID算法) 运行程序 我印象中好像做过Matlab/Simulink和AMEsim联合仿真的分享似的

能量项链,洛谷

解释:  环形dp问题还是考虑将环拉直,可以参考我上一篇文章:环形石子合并 [2 3 5 10 2] 3 5 10 将环拉直,[]内是一个有效的区间,可以模拟吸收珠子的过程,         如[2 3 5] <=>(2,3)(3,5)    2是头,3是中间,5是尾 len >= 3:因为最后[2 10 2]是最小的可以合并的有效区间 len <= n + 1因为[2 3

MATLAB中的矩阵在目标规划中的应用_以linprog为例

目标规划是一种数学规划方法,它允许在多个目标之间进行权衡,以找到最优解。 在MATLAB中,可以使用优化工具箱中的函数来求解目标规划问题。例如,`linprog` 函数可以用于求解线性规划问题,而 `fmincon` 函数可以用于求解有约束的非线性规划问题。对于多目标规划,可以使用 `fgoalattain` 函数来求解,该函数允许设置目标函数希望达到的目标值和权重。 在数学方程模型建立完成之

关于Java中Comparable和Comparator用于排序中的理解,以Comparable为例

在Java中,当一个对象实现了 `Comparable` 接口,这意味着该对象的类定义了一个自然的排序标准。`Comparable` 接口要求实现它的类必须实现 `compareTo` 方法,这个方法定义了对象之间的比较规则。 目录 一、为什么使用 `compareTo` 方法: 二、排序函数如何使用 `compareTo` 方法: 一、为什么使用 `compareTo`

Spring Boot实现大文件分块上传

1.分块上传使用场景 大文件加速上传:当文件大小超过100MB时,使用分片上传可实现并行上传多个Part以加快上传速度。 网络环境较差:网络环境较差时,建议使用分片上传。当出现上传失败的时候,您仅需重传失败的Part。 文件大小不确定: 可以在需要上传的文件大小还不确定的情况下开始上传,这种场景在视频监控等行业应用中比较常见。 2.实现原理 实现原理其实很简单,核心就是客户端把大文件

python爬取网页接口数据,以yearning为例

模拟登陆获取token,传token到对应的接口获取数据,下载到csv里面  import getpassimport osimport requestsimport timeimport csvfrom datetime import datetimeclass Yearning:def __init__(self):self.session = requests.Session()

洛谷P5490扫描线

0是最小的数字,将一个线段看成一个区间,对于一个矩形,从下扫到上,入边为1,而出边为-1,意思是将这个区间上的所有点加1(区间修改).把线段表示为Line[i],其中记录了l,r,h,tag,左右端点,高度,入边还是出边(1或-1) 那么每次区间修改后不为0的区间它的值可能是1,2,3或者是其它数字,这不好统计,可以将它转化一下,0是不是表示没有被覆盖过的地方,我们只要统计0的个数然后用总长减去