[POI2008] POC-Trains 题解
前言
题目链接:洛谷。
时间复杂度和输入同阶的做法。
题意简述
有 \(n\)(\(n \leq 10^3\))个长 \(m\) 的字符串,\(q\)(\(q \leq 10^5\))次操作,交换两个字符串的两个字符。问每个字符串在所有时刻,最多有几个和它相等。
题目分析
套路做法
看到字符串相等,想到使用哈希。但是要支持修改,怎么办呢?上数据结构! 当然不是,把哈希的结果看做一个数字,每次把某一位的值扣掉再加上就行了。即减去该位权值乘上原值,加上权值乘上修改后的值。
至于询问,每次操作后不可能 \(\Theta(n^2)\) 地弄。发现只会和当前修改的两个串有关,故 \(\Theta(n)\) 把这两个串的结果统计一遍。其他的串,维护一个 \(cur_i\) 表示当前 \(i\) 能和几个字符串相等。如果 \(i\) 和修改之前的串相等,就减去,如果和修改后的串相等了,再加上,最后和答案取个 \(\max\) 即可。
需要注意的是,当两个字符串是同一个的时候,可能需要小小特判。
时间复杂度 \(\Theta(nm + nq)\)。可过此题。当然,可以优化。
优化做法
尝试从哈希表角度分析。发现,一个字符串不断从一个相同哈希值的集合中,移动到另一个集合中,而答案就是这段时间内,该集合大小的最大值。如果考虑在删除的时候统计答案,就是在该元素加入集合后,该集合大小的最大值。
尝试把集合大小独立出来,看做单独的一个权值。我们可以把元素扩展,额外附加它在加入集合时集合的大小,发现这样删除操作就没有了,而是直接将所在集合那个附加的权值减一。这样查询,就成了一个后缀最大值。即从所在结点一直扫到哈希表末尾,取每个结点权值的最大值,得到这段时间该集合大小的最大值。
对于一条链,我们会扫过很多次,不妨从这里优化,即去除重复操作。想到,如果扫过了一个值,那下次不用再从原来的位置开始再扫一遍,而是把当前扫过来的最值记下来,下次直接从这里开始扫就行了。很类似并查集的路径压缩。
时间复杂度呢?每条边只会被经过一遍,和 \(n + q\) 是同阶的。总的时间复杂度是 \(\Theta(nm + q)\) 的,非常快。
代码
naive code
代码。
优化
哈希值域太小会 WA,所以使用了 unordered_map
,对时间复杂度没有影响。略去了快读,是最优解。
#include <cstdio>
#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;
const int N = 1010;
const int M = 110;
const int Q = 100010;
const int mod = 1e9 + 11;
const int bas = 1331;
inline int add(int a, int b) {
return a + b >= mod ? a + b - mod : a + b;
}
inline int sub(int a, int b) {
return a - b < 0 ? a - b + mod : a - b;
}
inline int mul(int a, int b) {
return 1ll * a * b % mod;
}
int n, m, q;
char str[N][M];
int hsh[N], res[N], pw[M];
int whr[N]; // 记录字符串对应哪一个结点
struct node {
int fa, ans;
} tree[N + Q * 2];
int tot;
unordered_map<int, int> ysiz, ylst; // 哈希对应的字符串有几个、上一个哈希值为它是哪个结点
int query(int x) { // 返回后缀最大值,并路径压缩
if (tree[x].fa == x) return tree[x].ans;
int res = query(tree[x].fa);
tree[x].fa = tree[tree[x].fa].fa;
return tree[x].ans = max(tree[x].ans, res);
}
void update(int x) {
int bl = hsh[x];
whr[x] = ++tot, ++ysiz[bl];
tree[tot] = {tot, ysiz[bl]};
tree[ylst[bl]].fa = tot;
ylst[bl] = tot;
}
signed main() {
fread(buf, 1, MAX, stdin);
read(n), read(m), read(q);
pw[0] = 1;
for (int i = 1; i <= m; ++i)
pw[i] = mul(pw[i - 1], bas);
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
hsh[i] = add(mul(hsh[i], bas), str[i][j] = readchar());
}
update(i);
}
for (int i = 1, a, x, b, y; i <= q; ++i) {
read(a), read(x), read(b), read(y);
res[a] = max(res[a], query(whr[a])), --ysiz[hsh[a]];
if (a != b) res[b] = max(res[b], query(whr[b])), --ysiz[hsh[b]];
hsh[a] = sub(hsh[a], mul(pw[m - x], str[a][x]));
hsh[b] = sub(hsh[b], mul(pw[m - y], str[b][y]));
swap(str[a][x], str[b][y]);
hsh[a] = add(hsh[a], mul(pw[m - x], str[a][x]));
hsh[b] = add(hsh[b], mul(pw[m - y], str[b][y]));
update(a);
if (a != b) update(b);
}
for (int i = 1; i <= n; ++i) {
printf("%d\n", max(res[i], query(whr[i]))); // 这里注意也要取 max
}
return 0;
}
[POI2008] POC-Trains 题解的更多相关文章
- 【BZOJ1125】[POI2008]Poc hash+map+SBT
[BZOJ1125][POI2008]Poc Description n列火车,每条有l节车厢.每节车厢有一种颜色(用小写字母表示).有m次车厢交换操作.求:对于每列火车,在交换车厢的某个时刻,与其颜 ...
- [POI2008]枪战Maf题解
问题 C: [POI2008]枪战Maf 时间限制: 1 Sec 内存限制: 256 MB 题目描述 有n个人,每个人手里有一把手枪.一开始所有人都选定一个人瞄准(有可能瞄准自己).然后他们按某个顺 ...
- 【luogu P3469 [POI2008]BLO-Blockade】 题解
题目链接:https://www.luogu.org/problemnew/show/P3469 #include <cstdio> #include <cstring> #i ...
- [POI2008] Poc (原名 Trians) Treap+Hash
这个题和千山鸟飞绝体现出了一种用平衡树解决动态集合问题,主要套路就是蜜汁标记. 这个题我一开始用替罪羊树搞了一下对了28个点,后来我换成了Treap一搞对了14个点,再后来发现被卡了Hash我竟然在自 ...
- bzoj1125:[POI2008]Poc
传送门 这个题好难卡啊. 看到这种题自然会想到字符串hash是不是,但是对于每次操作造成的影响需要\(O(n)\)的时间去更新,自然是不优的 可以发现这个更新可以用数据结构来维护,对于每个hash值开 ...
- Bzoj 1040 [ZJOI2008]骑士 题解
1040: [ZJOI2008]骑士 Time Limit: 10 Sec Memory Limit: 162 MBSubmit: 5368 Solved: 2044[Submit][Status ...
- bzoj AC倒序
Search GO 说明:输入题号直接进入相应题目,如需搜索含数字的题目,请在关键词前加单引号 Problem ID Title Source AC Submit Y 1000 A+B Problem ...
- [POI2008]KLO && POC
题意:给定一个序列 s1, s2,...sn,以及一个k,求一个连续的k个数,把s[i]...s[i+k-1]变成一个数s',使得sigma(|s[j]-s'|)(i<=j<=i+k-1) ...
- 【POI 每日题解 #4】 [POI2008]MAF-Mafia
[POI2008]MAF-Mafia 很容易看出是拓扑 但不容易想出来怎么做[可能是我太菜 首先 入度为零的人是肯定死不了的 接着 我们分成环和链分析 对于一个链 最多的情况就是顺着一个个开枪 最后剩 ...
- 【BZOJ1125】【POI2008】poc - splay+哈希
题意: Description n列火车,每条有l节车厢.每节车厢有一种颜色(用小写字母表示).有m次车厢交换操作.求:对于每列火车,在交换车厢的某个时刻,与其颜色完全相同的火车最多有多少. Inpu ...
随机推荐
- Linux使用docker搭建maven私有仓库
引言 在实际开发工作中,通常需要搭建maven私有仓库,今天就教大家如何搭建一套maven的私有仓库 Nexus介绍 Nexus 是Maven仓库管理器,如果你使用Maven,你可以从Maven中央仓 ...
- mongodb安装及启动配置文件,允许远程访问连接
windows安装https://www.runoob.com/mongodb/mongodb-window-install.html https://www.runoob.com/mongodb/m ...
- JSR303数据校验使用方法记录
JSR303并不对应着指定的jar包,而是一种规范,目前hibernate-validator是使用最多的是基于JSR303规范的实现 本文不适合新人观看,要求至少要知道使用方法 Springboot ...
- java ListMap使用多个或者任意个数的key进行排序
使用JAVA自己的排序方法,有的时候是一个可行的选择. 先从简单的开始说起. 一.少数key的情况 有一个需求:根据 menu_level,sort排序,越小的越前面. -- 下面代码按照升序规则进行 ...
- 将PaddleOCR 转为 ONNX 运行
PaddleOCR 是目前最好的开源OCR框架, 但paddle框架的兼容性实在不怎么好, 部署的时候容易出现各种各样的问题. 如果能把PaddleOCR转成ONNX, 就可以跳过paddle框架坑的 ...
- VScode连接服务器不用每次都输入密码
VScode连接服务器不用每次都输入密码. 用git或xcode的ssh keygen生成一组不带密码的 rsa2048 的公钥id_rsa_nopasswd.pub和私钥id_rsa_nopassw ...
- Linux内存不够了?看看如何开启虚拟内存增加内存使用量
1.为什么要使用虚拟内存 当我们没有多余的钱去购买大内存的云服务器时,但是当前服务器里面的软件和程序运行的比较多导致内存不够用了.这个时候可以通过增加虚拟内存来扩大内存容量.但是在启用虚拟内存时,需要 ...
- C# 时间戳与 标准时间互转
C# 时间戳与 标准时间的转其实不难,但需要注意下,基准时间的问题. 格林威治时间起点: 1970 年 1 月 1 日的 00:00:00.000 北京时间起点:1970 年 1 月 1 日的 08: ...
- yb课堂实战之首页banner轮播图和视频详情接口开发 《四》
开发轮播列表接口 VideoMapper.xml <?xml version="1.0" encoding="UTF-8" ?> <!DOCT ...
- springboot实现登录demo
实现简单的登录功能 实体类 定义实体类为User3类. 使用@Data:提供类的get,set,equals,hashCode,canEqual,toString方法: 使用@AllArgsConst ...