Solution -「Luogu 4135」作诗
写在前面 & 前置芝士
好像是好久没有打理 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」作诗的更多相关文章
- Solution -「Luogu 5170」类欧几里得算法
推柿子大赛了属于是. 题目要求三个柿子,不妨分别记为: \[\begin {align} f (a, b, c, n) &= \sum \limits _{i = 0} ^{n} \lfloo ...
- Solution -「Luogu 3959」 宝藏
果真是宝藏题目. 0x01 前置芝士 这道题我是真没往状压dp上去想.题目来源. 大概看了一下结构.盲猜直接模拟退火!\xyx 所需知识点:模拟退火,贪心. 0x02 分析 题目大意:给你一个图,可能 ...
- 「luogu4135」作诗
「luogu4135」作诗 传送门 分块好题. 预处理出 \(f[i][j]\) 表示 \(i\) 号块到 \(j\) 号块的答案,\(num[i][k]\) 表示 \(k\) 在前 \(i\) 块的 ...
- Solution -「ARC 104E」Random LIS
\(\mathcal{Description}\) Link. 给定整数序列 \(\{a_n\}\),对于整数序列 \(\{b_n\}\),\(b_i\) 在 \([1,a_i]\) 中等概率 ...
- Solution -「CTS 2019」「洛谷 P5404」氪金手游
\(\mathcal{Description}\) Link. 有 \(n\) 张卡牌,第 \(i\) 张的权值 \(w_i\in\{1,2,3\}\),且取值为 \(k\) 的概率正比于 \ ...
- Solution -「BZOJ 3812」主旋律
\(\mathcal{Description}\) Link. 给定含 \(n\) 个点 \(m\) 条边的简单有向图 \(G=(V,E)\),求 \(H=(V,E'\subseteq E)\ ...
- Solution -「CF 1342E」Placing Rooks
\(\mathcal{Description}\) Link. 在一个 \(n\times n\) 的国际象棋棋盘上摆 \(n\) 个车,求满足: 所有格子都可以被攻击到. 恰好存在 \(k\ ...
- 「 Luogu P1231 」 教辅的组成
题目大意 有 $\text{N1}$ 本书 $\text{N2}$本练习册 $\text{N3}$本答案,一本书只能和一本练习册和一本答案配对.给你一些书和练习册,书和答案的可能的配对关系.问你最多可 ...
- 「Luogu 1525」关押罪犯
更好的阅读体验 Portal Portal1: Luogu Portal2: LibreOJ Description \(S\)城现有两座监狱,一共关押着\(N\)名罪犯,编号分别为\(1 - N\) ...
随机推荐
- 99乘法表 java for循环
public static void main(String[] args) { //0-100的奇数和偶数和 int jsum=0; int osum=0; for (int i = 0; i &l ...
- 使用FastJson导出JSON
概述 fastjson是目前java语言中最快的json库,比自称最快的jackson速度要快,比gson快大约6倍. https://github.com/alibaba/fastjson Fast ...
- [题解] trip
题目大意 给定一颗大小为 \(N\) 的树, \(1\)的度数不小于 \(2\) .每个点有一个颜色,要么为黑色要么为白色. 从 \(1\) 号点开始游走,计数器初始为 \(0\). 如果当前为黑点计 ...
- 前端面试 -HTTP系列
http和https 的区别? 端口 经济 安全性 响应速度 http 80端口 不需要 明文传输,安全性差 页面响应速度快,使用tcp的3次握手 https 443端口 费钱SSL需要ca 证书 S ...
- 推荐三个好用的TamperMonkey洛谷插件
Part 1: TamperMonkey 插件 Part 1.1 什么是 Tampermonkey 在我们学习的过程中,往往想要更多功能,这时候可以使用 TamperMonkey 插件进行美化 官网介 ...
- java高级用法之:JNA中的回调
目录 简介 JNA中的Callback callback的应用 callback的定义 callback的获取和应用 在多线程环境中使用callback 总结 简介 什么是callback呢?简单点说 ...
- Java学习笔记-基础语法Ⅴ
学习一些Java常用的API Math:包含执行基本数字运算的方法 如果没有构造方法,一般类的成员都是静态的,通过类名可以直接调用 Java中有两种random函数,Math.Random()函数能够 ...
- 一起看 I/O | Flutter 休闲游戏工具包发布
作者 / Zoey Fan, Product Manager for Flutter, Google 对于大多数开发者来说,Flutter 是一个应用框架.但利用 Flutter 提供的硬件加速图形支 ...
- 一文学完Linux Shell编程,比书都好懂
一. Shell 编程 1. 简介 Shell 是一个用 C 语言编写的程序,通过 Shell 用户可以访问操作系统内核服务. Shell 既是一种命令语言,又是一种程序设计语言. Shell scr ...
- 对 Python 中 GIL 的一点理解
GIL(Global Interpreter Lock),全局解释器锁,是 CPython 为了避免在多线程环境下造成 Python 解释器内部数据的不一致而引入的一把锁,让 Python 中的多个线 ...