写在前面 & 前置芝士

  好像是好久没有打理 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. 1.2 Linux是什么,有哪些特点?

    与大家熟知的 Windows 操作系统软件一样,Linux 也是一个操作系统软件,其 logo 是一只企鹅(如图 1 所示).与 Windows 不同之处在于,Linux 是一套开放源代码程序的.可以 ...

  2. Shell 脚本编程最佳实践

    前言 由于工作需要,最近重新开始拾掇shell脚本.虽然绝大部分命令自己平时也经常使用,但是在写成脚本的时候总觉得写的很难看.而且当我在看其他人写的脚本的时候,总觉得难以阅读.毕竟shell脚本这个东 ...

  3. 记录在EF Core级联更新时出现的错误The database operation was expected to affect 1 row(s), but actually affected 0 row(s) (低级错误导致)

    错误提示:The database operation was expected to affect 1 row(s), but actually affected 0 row(s); data ma ...

  4. MySQL分库分表-理论

    分库分表的几种方式 把一个实例中的多个数据库拆分到不同的实例 把一个库中的表分离到不同的数据库中 数据库分片前的准备 在数据库并发和负载没有达到限制时,不推荐水平拆分 对一个库中的相关表进行水平拆分到 ...

  5. Calico网络插件

    以下大部分是本人参考各种资料{官方文档.书籍}对知识的汇总和整理,其中有理解错误的地方请大神留言和指正,嘿嘿~~ 1.概述 参考文档:https://projectcalico.docs.tigera ...

  6. 造个海洋球池来学习物理引擎【Three.js系列】

    github地址:https://github.com/hua1995116/Fly-Three.js 大家好,我是秋风.继上一篇<Three.js系列:   游戏中的第一/三人称视角>今 ...

  7. Unity中通过深度优先算法和广度优先算法打印游戏物体名

    前言:又是一个月没写博客了,每次下班都懒得写,觉得浪费时间.... 深度优先搜索和广度优先搜索的定义,网络上已经说的很清楚了,我也是看了网上的才懂的,所以就不在这里赘述了.今天讲解的实例,主要是通过自 ...

  8. Codeforces Round #746

    挺喜欢这场题目的 A: 水,不写了 B: Hemose Shopping 嘲讽自己一下啦~真的是caii 题意:一个数列,我们通过交换两个点(两点满足距离大于等于\(x\)),问能否排序成功. 思路: ...

  9. 耗时半年,Eoapi 终于正式发布 API 工具的插件广场

      这是我们的第一篇月报,每个月和每个来之不易的开发者分享产品故事以及产品进展. 在 5.20 这个极具中国特色的"节日"里,Eoapi 发布了 1.0.0 版,三个程序员掉了半年 ...

  10. DirectX11 With Windows SDK--19(Dev) 编译Assimp并加载模型、新的Effects框架

    前言 注意:这一章进行了重写,对应教程Dev分支第19章的项目,在更新完后面的项目后会替换掉原来第19章的教程 在前面的章节中我们一直使用的是由代码生成的几何模型,但现在我们希望能够导入模型设计师生成 ...