写在前面 & 前置芝士

  好像是好久没有打理 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. 浅谈 UNIX、Linux、ios、android 他们之间的关系

    开源Linux 一个执着于技术的公众号 Unix, 简化形成了Linux,Linux则是Android的内核,而苹果则是使用unix系统作为ios和macos的内核. 几个系统出现的时间 UNIX系统 ...

  2. 专门为小白准备的入门级mybatis-plus-generator代码自动生成器,提高开发效率。值得收藏

    引入依赖 <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-g ...

  3. 攻防世界web进阶题—unfinish

    攻防世界web进阶题-unfinish 1.看一下题目提示SQL 2.打开题目看一下源码,没有问题 3.查一下网站的组成:php+Apache/2.4.7+Ubuntu 4.扫一下目录,扫到一个注册页 ...

  4. 爬虫--Scrapy框架的初步使用

    1.scrapy在windows环境下安装 - 环境的安装: a. pip3 install wheel b. 下载twisted: http://www.lfd.uci.edu/~gohlke/py ...

  5. 命令行参数 getopt模块

    getopt中的函数: getopt.getopt(sys.argv[1:], shortopts, longopts=[]) args指的是当前脚本接收的参数,它是一个列表,可以通过sys.argv ...

  6. nodejs使用 svg-captcha 做验证码及验证

    一.需求 使用 nodejs 做后端开发,需要请求验证码,在 github 上看到了 svg-captcha 这个库,发现他是将 text 转 svg 进行返回的,安全性也有保证,不会被识别成文字. ...

  7. hadoop联合hive基础使用

    sqoop路径:/opt/module/sqoop 把指定文件放到hadoop指定路径:hadoop fs -put stu1.txt /user/hive/warehouse/stu hive启动( ...

  8. iOS全埋点解决方案-数据存储

    前言 ​ SDK 需要把事件数据缓冲到本地,待符合一定策略再去同步数据. 一.数据存储策略 ​ 在 iOS 应用程序中,从 "数据缓冲在哪里" 这个纬度看,缓冲一般分两种类型. 内 ...

  9. dubbo是如何实现可扩展的?

    dubbo如何实现可扩展的,援引官网描述: Dubbo 的扩展点加载从 JDK 标准的 SPI (Service Provider Interface) 扩展点发现机制加强而来. Dubbo 改进了 ...

  10. CF1681F Unique Occurrences

    题意:一棵树,问每条路径上只出现一次的值的个数的和. 思路: 显然想到考虑边贡献.每条边权下放到下面的哪个点.\(up_i\)为上面第一个点权等于它的点.我们需要一个子树内点权等于它的点(如果满足祖孙 ...