写在前面 & 前置芝士

  好像是好久没有打理 blog 了。感觉上学期是有点颓。嘶,初三了好好冲一次吧。

  那么回到这道题目。你会分块就能看懂。

题目大意

  先挂个来自洛谷的 link

  大概就说的是,给定一个长度为 \(n\) 的整数序列 \(A\),有 \(m\) 次询问,每次询问查询一个区间 \([l, r]\),试问有多少个数再该区间中出现了偶数次。

  规定 \(\forall a_i \leq c (i \in [1, n])\), 且有 \(1 \leq n, m, c \leq 10^5\)。

题目解析

  开题。其实根据这个数据范围和包容卡常的时限,不难想到是某种大数据结构。

  基本思路给到分块。我们从查询出发考虑块需要记录的信息种类。

  对于一个分属于两个不同块 \(x, y\) 的 \(l, r\),我们分成三个部分计算。即连续完整的块的部分,左边多出来的部分,右边多出来的部分。

  后两个部分是非常好解决的,暴力搞就行。而对于第一个我们需要想到一个能和其他两部分合并且时间复杂度较小的处理方法。

  考虑记录每个数在块内个数的前缀和,即 \(sum_{i, j}\) 表示在前 \(i\) 个块中 \(j\) 出现了多少次,显然这个可以 \(O(c \sqrt{n}))\) 轻松做到。

  这样就可以求出多余部分每个数在整个区间出现的次数,不难得出答案。

  但这样会漏计算一种情况,连续的完整的块的部分内可能会内卷一些数,对答案产生贡献,而这些数在多出的部分是没有的。

  于是我们考虑预处理一个 \(dp_{i, j}\) 表示第 \(i\) 个块到第 \(j\) 个块的答案。

  但如果直接将 \(dp_{x + 1, y - 1}\) 加入答案可能会导致一些假的答案贡献,例如 \(num\) 在多出来的部分中出现了奇数次,在完整连续的块的部分中出现了偶数次,那么它在整个区间中其实是出现了奇数次,但它仍被算入了答案。

  于是我们考虑将一次询问的返回值 \(ret\) 优先赋值为 \(dp_{x + 1, y - 1}\)。

  再遍历多余部分的每个数 \(a_i\),在遍历的同时记录遍历到当前这的数次数 \(tot_{a_i}\)。

  如果 \(tot_{a_i} + sum_{y - 1, a_i} - sum_{x, a_i}\) 为奇数,且大于 \(1\),则说明这个数曾经造成过贡献,但现在这个贡献伪了,所有这个时候我们可以直接 \(ret--\)。

  否则,如果 \(tot_{a_i} + sum_{y - 1, a_i} - sum_{x, a_i}\) 为偶数,就 \(ret++\) 即可。

  显然若 \(l, r\) 属于同块也可以用类似思路求到答案。

  (分段给个码。

// tot 是桶。
int Query(int l, int r) {
if (pos[l] == pos[r] || pos[l] + 1 == pos[r]) {
int ret = 0;
for (int i = l; i <= r; i++) {
tot[a[i]]++;
if (tot[a[i]] & 1) {
if (tot[a[i]] > 1)
ret--;
} else
ret++;
}
for (int i = l; i <= r; i++) tot[a[i]] = 0;
return ret;
}
int x = pos[l], y = pos[r], ret = dp[x + 1][y - 1];
for (int i = l; i <= q[x].r; i++) {
tot[a[i]]++;
int cnt = sum[y - 1][a[i]] - sum[x][a[i]];
if (((tot[a[i]] + cnt) & 1)) {
if (tot[a[i]] + cnt > 1)
ret--;
} else
ret++;
}
for (int i = q[y].l; i <= r; i++) {
tot[a[i]]++;
int cnt = sum[y - 1][a[i]] - sum[x][a[i]];
if (((tot[a[i]] + cnt) & 1)) {
if (tot[a[i]] + cnt > 1)
ret--;
} else
ret++;
}
for (int i = l; i <= q[x].r; i++) tot[a[i]] = 0;
for (int i = q[y].l; i <= r; i++) tot[a[i]] = 0;
return ret;
}

  最后考虑一个遗留问题,如何预处理出 \(dp_{i, j}\) ??

  我们每次将 \(i\) 固定下来,然后枚举 \(j\),并遍历每一个编号为 \(i\) 到 \(j\) 的块,暴力用桶记录出现个数即可。

  这个很简单就可以玩到 \(n \sqrt{n}\)。

// tot 是桶。
for(int i = 1; i <= len; i++) {
int j = i, cnt = 0;
while(j <= len) {
for(int k = q[j].l; k <= q[j].r; k++) {
tot[a[k]]++;
if((tot[a[k]] & 1)) {
if(tot[a[k]] > 1)
cnt--;
}
else
cnt++;
}
dp[i][j] = cnt;
j++;
}
for(int j = i; j <= n; j++)
tot[a[j]] = 0;
}

  回顾一下,预处理连续的块的一些信息来解决问题的方法好像很套路。

  但泛用性好像很广的样子。以后分块的题不妨多往这方面想想。

  (www Stardust 最喜欢 BB 了。

  最后给个完整的。

#include <cmath>
#include <cstdio> int Max(int x, int y) { return x > y ? x : y; }
int Min(int x, int y) { return x < y ? x : y; }
int Abs(int x) { return x < 0 ? -x : x; } int read() {
int k = 1, x = 0;
char s = getchar();
while (s < '0' || s > '9') {
if (s == '-')
k = -1;
s = getchar();
}
while (s >= '0' && s <= '9') {
x = (x << 3) + (x << 1) + s - '0';
s = getchar();
}
return x * k;
} void write(int x) {
if (x < 0) {
putchar('-');
x = -x;
}
if (x > 9)
write(x / 10);
putchar(x % 10 + '0');
} void print(int x, char s) {
write(x);
putchar(s);
} const int MAXN = 1e5 + 5;
const int MAXM = 320; struct node {
int l, r;
node() {}
node(int L, int R) {
l = L;
r = R;
}
} q[MAXM]; int n, c, m;
int ans[MAXM][MAXN], sum[MAXM][MAXN], dp[MAXM][MAXM], pos[MAXN], a[MAXN], tot[MAXN]; int Query(int l, int r) {
if (pos[l] == pos[r] || pos[l] + 1 == pos[r]) {
int ret = 0;
for (int i = l; i <= r; i++) {
tot[a[i]]++;
if (tot[a[i]] & 1) {
if (tot[a[i]] > 1)
ret--;
} else
ret++;
}
for (int i = l; i <= r; i++) tot[a[i]] = 0;
return ret;
}
int x = pos[l], y = pos[r], ret = dp[x + 1][y - 1];
for (int i = l; i <= q[x].r; i++) {
tot[a[i]]++;
int cnt = sum[y - 1][a[i]] - sum[x][a[i]];
if (((tot[a[i]] + cnt) & 1)) {
if (tot[a[i]] + cnt > 1)
ret--;
} else
ret++;
}
for (int i = q[y].l; i <= r; i++) {
tot[a[i]]++;
int cnt = sum[y - 1][a[i]] - sum[x][a[i]];
if (((tot[a[i]] + cnt) & 1)) {
if (tot[a[i]] + cnt > 1)
ret--;
} else
ret++;
}
for (int i = l; i <= q[x].r; i++) tot[a[i]] = 0;
for (int i = q[y].l; i <= r; i++) tot[a[i]] = 0;
return ret;
} int main() {
n = read(), c = read(), m = read();
for (int i = 1; i <= n; i++) a[i] = read();
int te = sqrt(n), len = n / te;
for (int i = 1; i <= len; i++) {
q[i].l = (i - 1) * te + 1;
q[i].r = i * te;
for (int j = q[i].l; j <= q[i].r; j++) {
pos[j] = i;
ans[i][a[j]]++;
}
}
if (q[len].r < n) {
q[len + 1].l = q[len].r + 1;
q[++len].r = n;
for (int j = q[len].l; j <= q[len].r; j++) {
pos[j] = len;
ans[len][a[j]]++;
}
}
for (int i = 1; i <= len; i++)
for (int j = 0; j <= c; j++) sum[i][j] = sum[i - 1][j] + ans[i][j];
for (int i = 1; i <= len; i++) {
int j = i, cnt = 0;
while (j <= len) {
for (int k = q[j].l; k <= q[j].r; k++) {
tot[a[k]]++;
if ((tot[a[k]] & 1)) {
if (tot[a[k]] > 1)
cnt--;
} else
cnt++;
}
dp[i][j] = cnt;
j++;
}
for (int j = i; j <= n; j++) tot[a[j]] = 0;
}
// for(int i = 1; i <= len; i++)
// for(int j = i; j <= len; j++)
// printf("dp[%d][%d] = %d\n", i, j, dp[i][j]);
int last = 0;
for (int i = 1, l, r; i <= m; i++) {
l = read(), r = read();
l = (l + last) % n + 1;
r = (r + last) % n + 1;
if (l > r) {
int t = l;
l = r;
r = t;
}
print(last = Query(l, r), '\n');
}
return 0;
} // Ethereal Stardust

Solution -「Luogu 4135」作诗的更多相关文章

  1. Solution -「Luogu 5170」类欧几里得算法

    推柿子大赛了属于是. 题目要求三个柿子,不妨分别记为: \[\begin {align} f (a, b, c, n) &= \sum \limits _{i = 0} ^{n} \lfloo ...

  2. Solution -「Luogu 3959」 宝藏

    果真是宝藏题目. 0x01 前置芝士 这道题我是真没往状压dp上去想.题目来源. 大概看了一下结构.盲猜直接模拟退火!\xyx 所需知识点:模拟退火,贪心. 0x02 分析 题目大意:给你一个图,可能 ...

  3. 「luogu4135」作诗

    「luogu4135」作诗 传送门 分块好题. 预处理出 \(f[i][j]\) 表示 \(i\) 号块到 \(j\) 号块的答案,\(num[i][k]\) 表示 \(k\) 在前 \(i\) 块的 ...

  4. Solution -「ARC 104E」Random LIS

    \(\mathcal{Description}\)   Link.   给定整数序列 \(\{a_n\}\),对于整数序列 \(\{b_n\}\),\(b_i\) 在 \([1,a_i]\) 中等概率 ...

  5. Solution -「CTS 2019」「洛谷 P5404」氪金手游

    \(\mathcal{Description}\)   Link.   有 \(n\) 张卡牌,第 \(i\) 张的权值 \(w_i\in\{1,2,3\}\),且取值为 \(k\) 的概率正比于 \ ...

  6. Solution -「BZOJ 3812」主旋律

    \(\mathcal{Description}\)   Link.   给定含 \(n\) 个点 \(m\) 条边的简单有向图 \(G=(V,E)\),求 \(H=(V,E'\subseteq E)\ ...

  7. Solution -「CF 1342E」Placing Rooks

    \(\mathcal{Description}\)   Link.   在一个 \(n\times n\) 的国际象棋棋盘上摆 \(n\) 个车,求满足: 所有格子都可以被攻击到. 恰好存在 \(k\ ...

  8. 「 Luogu P1231 」 教辅的组成

    题目大意 有 $\text{N1}$ 本书 $\text{N2}$本练习册 $\text{N3}$本答案,一本书只能和一本练习册和一本答案配对.给你一些书和练习册,书和答案的可能的配对关系.问你最多可 ...

  9. 「Luogu 1525」关押罪犯

    更好的阅读体验 Portal Portal1: Luogu Portal2: LibreOJ Description \(S\)城现有两座监狱,一共关押着\(N\)名罪犯,编号分别为\(1 - N\) ...

随机推荐

  1. 浅尝Spring注解开发_Bean生命周期及执行过程

    Spring注解开发 浅尝Spring注解开发,基于Spring 4.3.12 包含Bean生命周期.自定义初始化方法.Debug BeanPostProcessor执行过程及在Spring底层中的应 ...

  2. HTML5 Canvas 超逼真烟花绽放动画

    各位前端朋友们,大家好!五一假期即将结束,在开启加班模式之前,我要给大家分享一个超酷超逼真的HTML5 Canvas烟花模拟动画.这次升级版的烟花动画有以下几个特点: 烟花绽放时,将展现不同的色彩,不 ...

  3. web安全之自己写一个扫描器

    web安全之自己写一个扫描器 自己来写一个简单的目录扫描器,了解扫描器的运转机制和原理,因为python写脚本比较容易所以用python写一个网站目录扫描器. 第一步:我们需要导入所需要的库 1 im ...

  4. 使用本地自签名证书为 React 项目启用 https 支持

    简介 现在是大前端的时代,我们在本地开发 React 项目非常方便.这不是本文的重点,今天要分享一个话题是,如何为这些本地的项目,添加 https 的支持.为什么要考虑这个问题呢?主要有几个原因 如果 ...

  5. ELF文件结构

    ELF文件结构 ELF文件的全称是Executable and Linkable Format,直译为"可执行可链接格式",包括目标文件(.o).可执行文件(可以直接运行).静态链 ...

  6. SSO 方案演进

    背景介绍 随着业务与技术的发展,现今比以往任何时候都更需要单点登录 SSO 身份验证. 现在几乎每个网站都需要某种形式的身份验证才能访问其功能和内容. 随着网站和服务数量的增加,集中登录系统已成为一种 ...

  7. opencv学习之边缘检测

    边缘检测 是图像处理 过程中经常会涉及到的一个环节.而在计算机视觉 和 机器学习领域,边缘检测 用于 特征提取 和 特征检测 效果也是特别明显.而 openCV 中进行边缘检测的 算法 真是五花八门, ...

  8. 让 API 测试变的简单。

    做开发已经四年有余了,之前在接口测试的时候最开始用的自己写的测试类进行测试,后来接触到了 postman 和 swagger ,虽然用起来比自己写的强太多了,但是总觉得差点事儿. 一方面是 postm ...

  9. Linux内网渗透

    Linux虽然没有域环境,但是当我们拿到一台Linux 系统权限,难道只进行一下提权,捕获一下敏感信息就结束了吗?显然不只是这样的.本片文章将从拿到一个Linux shell开始,介绍Linux内网渗 ...

  10. 产品揭秘】来也Lead 2022产品亮点解读-RPA学习天地

    2022年4月26日,来也举行新品发布会.作为技术人员,花里胡哨的我且不说,我且说技术相关.整体架构"概念"整个平台覆盖了智能自动化的全生命周期包含:业务理解.流程创建.随处运行. ...