写在前面 & 前置芝士

  好像是好久没有打理 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. Android8.0 后台服务保活的一种思路

    原文地址:Android8.0 后台服务保活的一种思路 | Stars-One的杂货小窝 项目中有个MQ服务,需要一直连着,接收到消息会发送语音,且手机要在锁屏也要实现此功能 目前是使用广播机制实现, ...

  2. 关于利用STL栈求解四则中缀表达式以及中缀表达式转逆波兰表达式和逆波兰表达式的求解

    今天总结一下栈的一个重要应用---四则数学表达式的求解 数学表达式的求解是栈的一个重要的应用,在计算机的应用中 如果求解一个四则运算表达式,我们可能会直接写一个程序例如什么printf("% ...

  3. Java学习笔记-基础语法ⅩⅠ-UDP、TCP

    网络编程 三要素:IP地址.端口.协议 IP地址:使用ipconfig查看,如果装了VM的话,会有VMnet1.VMnet8和WLAN,net1不能从虚拟机到主机,net8不能从主机到虚拟机,net0 ...

  4. SecureCRT使用SSH链接出现Password Authentication Failed,Please verify that the username and password are correct的解决办法(亲测有效)

  5. docker+nginx+redis部署前后端分离项目!!!

    介绍本文用的经典的前后端分离开源项目.项目的拉取这些在另一篇博客!!! 其中所需要的前后端打包本篇就不做操作了!!不明白的去看另一篇博客!!! 地址:http://www.cnblogs.com/ps ...

  6. windows 存储和切换 ip 配置

    我的虚拟机用的是桥接模式,在公司使用时设置的是静态 ip,但网段和家里面的不一样,就导致在公司和家里,我需要频繁修改 ipv4 的配置以适应不同的网络环境 Simple-IP-Config 工具解决了 ...

  7. mapstruct 的 mapstruct-processor 自动生成的 Impl 文件中未设置属性值(时好时坏)

    配置依赖和注解处理器 ... <properties> <org.mapstruct.version>1.4.2.Final</org.mapstruct.version ...

  8. 144_Power Pivot贷款之等额本息与等额本金

    博客:www.jiaopengzi.com 焦棚子的文章目录 请点击下载附件 一.背景 买房贷款的时候会遇到等额本息与等额本金的问题,今天做了一个两者对比,看看如何选择,来一张对比图. 等额本息的前期 ...

  9. 我使用Spring AOP实现了用户操作日志功能

    我使用Spring AOP实现了用户操作日志功能 今天答辩完了,复盘了一下系统,发现还是有一些东西值得拿出来和大家分享一下. 需求分析 系统需要对用户的操作进行记录,方便未来溯源 首先想到的就是在每个 ...

  10. 羽夏笔记—— AT&T 与 GCC

    写在前面   本文是本人根据<AT&T 汇编语言与 GCC 内嵌汇编简介>进一步整理,修改了一些错误,并删除我并不能复现代码相关的部分.该文章一是我对 AT&T 的学习记录 ...