CF533F Encoding 题解
提示1:
\(\mathcal O(26^2*n)\) 的算法可通过。常用的几种字符串匹配算法kmp,AC自动机,哈希都可以解决该问题 (后两者可以优化到 \(\mathcal O(26*n)\) )。
提示2:
将文本串 \(S\) 和模式串 \(T\) 中的 \(26\) 个字母分开考虑,各自匹配。
\(\mathcal O(26^2*n)\) 的kmp匹配
枚举每一种转换,记为 \((x,y),x,y\in\{'a','b',\cdots,'z'\}\),\(x\) 可以等于 \(y\) ,即不转换。如果该转换在某区间 \(S[l,r]\) 是与 \(T\) 匹配的,则说明在 \(S[l,r]\) 中每个 \(x\) 位置和 \(T\) 中 \(y\) 位置一一对应,每个 \(y\) 位置和 \(T\) 中 \(x\) 位置一一对应,而且不会有另一个关于 \(x\) 的转换使得该区间匹配。 若对于 \(S[l,r]\) 段上 \(x\) 的任何一种转换都不能与 \(T\) 匹配,则说明该段无法匹配;否则在该区间起点打标记,表示该转换匹配成功。对于 \(T\) 任何一个字符 \(x\) 都需要有转换在 \(S[l,r]\) 中匹配,才说明 \(S[l,r]\) 是与 \(T\) 匹配的。
具体实现时令 \(T\) 中 \(x,y\) 互换,其他位置替换为 \(0\),\(S\) 中除 \(x,y\) 以外的位置置为 0,其它保持不变,这样可以用kmp判断转换 \((x,y)\) 是否与 \(S\) 上某一段匹配。例如:
原字符串 | 转换 \((b,c)\) ,变成只含 \(0,b,c\) 的序列(只在 \(T\) 转换 \((b,c)\)) | |
---|---|---|
\(S\) | \(abcab\) | \(0~b~c~0~b\) |
\(T\) | \(acbac\) | \(0~b~c~0~b\) |
这里 \(a,b,c\) 都能找到转换与 \(S\) 匹配(\(a\) 不转换就可以匹配),而对于 \(S,T\) 都没出现的字母,任意转换都是匹配的,即会把两个序列都变成全 \(0\) ,肯定是匹配的。
\(Code:\)
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
int sl,tl,nxt[N],ss[N],tt[N];
bool p[N][27];//p[i][j]标记S[i,i+tl-1]这段上是否有关于x的转换使得与T串匹配
char s[N],t[N];
vector<int>ans;
int main() {
scanf("%d %d %s %s",&sl,&tl,s+1,t+1);
for(int x = 'a'; x <= 'z'; x++){
for(int y = x; y <= 'z'; y++){
//t中x,y互换
for(int i = 1;i <= tl;i++) {
if(t[i] == x) tt[i] = y;
else if(t[i] == y) tt[i] = x;
else tt[i] = 0;
}
for(int i = 1; i <= sl; i++) ss[i] = ( (s[i] == y)||(s[i] == x) )? s[i] : 0;
//kmp匹配
for(int i = 2,j = 0; i <= tl; i++){
while(j && tt[i] != tt[j + 1]) j = nxt[j];
if(tt[i] == tt[j + 1]) nxt[i] = ++j;
else nxt[i] = 0;
}
for(int i = 1,j = 0; i <= sl; i++){
while(j && ss[i] != tt[j + 1]) j = nxt[j];
if(ss[i] == tt[j + 1]) ++j;
if(j == tl) {
p[i-tl+1][x-'a'] = 1;
p[i-tl+1][y-'a'] = 1;
j = nxt[j];
}
}
}
}
//O(26*n) 的判断:S[i,i+tl-1]是否可以和转换后的T匹配,要满足t的每个字符j都有转换与S[i,i+tl-1]匹配
for(int i = 1;i <= sl-tl+1;i++) {
int flag = 1;
for(int j = 0;j < 26;j++) if(!p[i][j]) { flag = 0;break;}
if(flag) ans.push_back(i);
}
printf("%d\n",ans.size());
for(auto it : ans) printf("%d ",it);
return 0;
}
\(\mathcal O(26*n)\) 的哈希匹配
在kmp匹配结束后,有一个 \(\mathcal O(26*n)\) 的判断循环,因为在kmp中已经求出 \(p_{i,j}\) 的值了,所以对于每个 \(i\) ,有 \(26\) 次 \(\mathcal O(1)\) 的判断 。我们考虑,哈希也可以 \(\mathcal O(1)\) 的判断两个串是否匹配,这里只要将 \(26\) 个字母分别求哈希值再求和匹配就可以了,所以哈希是可以将整体复杂度优化到 \(\mathcal O(26*n)\) 的。
具体实现时需要求出一个特殊的数组,我们记为 \(nxt_{i,j}\) ,表示 \(S[i,|s|]\) 上第一次出现字符 \(j\) 的下标。这样在我们判断 \(S[i,i+|t|-1]\) 这段区间是否可以通过转换 \(j\) 使得与 \(T\) 匹配时,我们先通过 \(nxt_{i,j}\) 获得 \(j\) 下标,如果它在 \([i,i+|t|-1]\) 这个区间上,说明它在 \(T\) 串对应位置有一个字符 \(y\) 需要与它匹配,如果我们把 \([i,i+|t|-1]\) 上的 \(j\) 全部替换为 \(y\) ,其他字符置为 \(0\) 。对所有的 \(26\) 个字符都这样处理,他们分别计算的哈希值加起来与 \(T\) 的哈希值比较,就可以知道 \(S[i,i+|t|-1]\) 是否可以和 \(T\) 匹配了。这样对于每个 \(i\) 的 \(26\) 次计算都是 \(\mathcal O(1)\) 的。注意对于同一区间上的字符转换不能有交集,即两个关于 \(x\) 的转换 \((x,y),(x,z),y\not=z\) 不能同时存在,代码中用 \(vis\) 记录每个字符的转换对象,每个字符的 \(vis_j\) 只能被赋值一次。因为比较懒没写双哈希。
\(Code:\)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 5;
const int base = 131;
const int mod = 19260817;
int sl,tl,nxt[N][26];
ll T_hash,s_hash[N][26],fac[N];
int vis[27];
vector<int>ans;
char s[N],t[N];
int main() {
scanf("%d %d %s %s",&sl,&tl,s+1,t+1);
fac[0] = 1;
for(int i = 1;i <= sl;i++) fac[i] = fac[i-1] * base % mod;
//预处理出nxt数组,记录i后第一个字符j的下标
for(int j = 0;j < 26;j++) nxt[sl+1][j] = sl+1;
for(int i = sl;i >= 1;i--)
for(int j = 0;j < 26;j++){
if(s[i] == j+'a') nxt[i][j] = i;
else nxt[i][j] = nxt[i+1][j];
}
//求出T串hash值
for(int i = 1;i <= tl;i++) T_hash = (T_hash * base + (t[i]-'a') ) % mod;
//求出S串26个字符分开计算的hash值,注意这里是将为j的字符置为1,其他置为0作哈希,之后有(j,y)的转换乘上y即可(哈希满足乘法分配律)
for(int i = 1;i <= sl;i++)
for(int j = 0;j < 26;j++){
if(s[i] == j+'a') s_hash[i][j] = (s_hash[i-1][j] * base + 1) % mod;
else s_hash[i][j] = s_hash[i-1][j] * base % mod;
}
for(int i = 1;i <= sl-tl+1;i++) {
ll tot_hash = 0;
memset(vis,-1,sizeof vis); //-1表示未被赋值过
for(int j = 0;j < 26;j++){
int p = nxt[i][j];
if(p > i + tl - 1) continue;
int y = t[p-i+1]-'a'; //找到在T串对应位置的字符
//只有之前x,y都没有被转换过才能赋值
if(vis[j] == -1 && vis[y] == -1) vis[y] = j,vis[j] = y;
tot_hash = (tot_hash + vis[j] * ((s_hash[i+tl-1][j] - s_hash[i-1][j] * fac[tl]) % mod + mod) ) % mod;
}
if(tot_hash == T_hash) ans.push_back(i);
}
printf("%d\n",ans.size());
for(auto it :ans) printf("%d ",it);
return 0;
}
\(\mathcal O(26*n)\) 的AC自动机匹配
思路来自 【题解】Codeforces533F. Encoding 按字符分解,AC自动机 。其实与哈希匹配思想是一致的,都是想要找到一种方式可以做到对于每个起点 \(i\) ,进行 \(26\) 次复杂度只有 \(\mathcal O(1)\) 的判断。我们把 \(T\) 串分成 \(26\) 个 \(01\) 串(不计入全 \(0\) 串),构建AC自动机。再将 \(S\) 串分成 \(26\) 个 \(01\) 串,在AC自动机上作匹配,每次匹配到一个 \(T\) 串末尾,就在该位置标记匹配的字符,对于某个 \(i\) ,要求 \(T\) 中 \(26\) 个字符 \(j\) 都能被匹配到末尾(除 \(j\) 全 \(0\) 串外),才说明该段和 \(T\) 匹配。例如:
原字符串 | \(a\) 的 \(01\) 串 | \(b\) 的 \(01\) 串 | \(c\) 的 \(01\) 串 | |
---|---|---|---|---|
\(S\) | \(abcab\) | \(1~0~0~1~0\) | \(0~1~0~0~1\) | \(0~0~1~0~0\) |
\(T\) | \(acbac\) | \(1~0~0~1~0\) | \(0~0~1~0~0\) | \(0~1~0~0~1\) |
这里 \(S\) 的 \(a\) 串会匹配到AC自动机上对应 \(T\) 的 \(a\) 串末端, \(S\) 的 \(b\) 串会匹配到AC自动机上对应 \(T\) 的 \(c\) 串末端, \(c\) 串会匹配到AC自动机上对应 \(T\) 的 \(b\) 串末端,其他全为 \(0\) 的串不插入AC自动机,所以 \(S\) 和 \(T\) 是匹配的。
AC自动机构建复杂度是 \(\mathcal O (26*n)\) 的,匹配时无需暴力跳fail,对于 \(26\) 个串的匹配也是 \(\mathcal O (26*n)\) 。
\(Code:\)
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
int sl,tl,tt[N],en[N*26], match[27];
bool has[27];
char s[N],t[N];
vector<int>ans;
int trie[N*26][2],cnt,fail[N*26],now[26];
void insert(int o){
int now = 0;
for (int i = 1,c; i <= tl;i++){
c = tt[i];
if(trie[now][c] == 0) trie[now][c] = ++cnt;
now = trie[now][c];
}
en[now] = o;
}
void build(){
queue<int> q;
for (int i = 0; i < 2;i++) if(trie[0][i]) q.push(trie[0][i]);
while(!q.empty()){
int now = q.front();q.pop();
for (int i = 0; i < 2;i++){
if(trie[now][i]){
q.push(trie[now][i]);
fail[trie[now][i]] = trie[fail[now]][i];
}else
trie[now][i] = trie[fail[now]][i];
}
}
}
int main() {
scanf("%d %d %s %s",&sl,&tl,s+1,t+1);
memset(en,-1,sizeof en);
for(int j = 0;j < 26;j++){
for(int i = 1; i <= tl; i++)
has[j] |= tt[i] = (t[i] == j + 'a');
if(has[j]) insert(j); //不是全0串才插入AC自动机
}
build();
for(int i = 1;i <= sl;i++){
int flag = 0;
memset(match,-1,sizeof match);
for(int j = 0;j < 26;j++){
int c = s[i]-'a';
now[j] = trie[now[j]][c == j];
if(en[now[j]] != -1) match[en[now[j]]] = j;//记录T末端字符en[now[j]]匹配到S的字符j
}
for(int j = 0;j < 26;j++)
if(has[j]){
if(match[j] == -1) {flag = 1;break;}
int y = match[j];
//在T串同时含j,y的情况下,match[j]=y,match[y]=j才满足变换后匹配
if(match[y] != j && has[y]) {flag = 1;break;}
}
if(!flag) ans.push_back(i-tl+1);
}
printf("%d\n",ans.size());
for(auto it : ans) printf("%d ",it);
return 0;
}
这道题可以用多种算法尝试解决,是一道很全面的题目。各算法运行结果如下(空间换时间了):
CF533F Encoding 题解的更多相关文章
- [Bugku]Web题解
bugku地址链接:https://ctf.bugku.com 1.web2 浏览器就显示一堆动态笑脸,时间长了密集恐惧症了. 解法1: F12查看源码 解法2: 地址栏输入: view-source ...
- 2016 华南师大ACM校赛 SCNUCPC 非官方题解
我要举报本次校赛出题人的消极出题!!! 官方题解请戳:http://3.scnuacm2015.sinaapp.com/?p=89(其实就是一堆代码没有题解) A. 树链剖分数据结构板题 题目大意:我 ...
- noip2016十连测题解
以下代码为了阅读方便,省去以下头文件: #include <iostream> #include <stdio.h> #include <math.h> #incl ...
- BZOJ-2561-最小生成树 题解(最小割)
2561: 最小生成树(题解) Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 1628 Solved: 786 传送门:http://www.lyd ...
- Codeforces Round #353 (Div. 2) ABCDE 题解 python
Problems # Name A Infinite Sequence standard input/output 1 s, 256 MB x3509 B Restoring P ...
- javac -encoding utf8 in linux
由于另外负责编码的同事用的是utf-8,我用的默认的编码格式gbk,在提交代码时,为了迁就他,我打算把格式用工具转成utf-8. 转化成果后,然后在make一下,发现javac -encoding u ...
- 哈尔滨理工大学ACM全国邀请赛(网络同步赛)题解
题目链接 提交连接:http://acm-software.hrbust.edu.cn/problemset.php?page=5 1470-1482 只做出来四道比较水的题目,还需要加强中等题的训练 ...
- 2016ACM青岛区域赛题解
A.Relic Discovery_hdu5982 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/65536 K (Jav ...
- 创建Odoo8数据库时的“new encoding (UTF8) is incompatible with the encoding of the template database (SQL_ASCII)“问题
Odoo8创建数据库时,显示如下错误信息: DataError: new encoding (UTF8) is incompatible with the encoding of the templa ...
随机推荐
- MySQL 索引的类型
主键索引(PRIMARY KEY) # 主键 = 唯一键索引 + 非空 + 只能设置一个字段 # 创建表的时候创建主键索引 mysql> create table test(id int not ...
- JPG学习笔记1(附完整代码)
#topics h2 { background: rgba(43, 102, 149, 1); border-radius: 6px; box-shadow: 0 0 1px rgba(95, 90, ...
- vscode remote Development ssh targets 连接腾讯云
配置 本地安装 插件 remote development 点击左侧,选择ssh 点击设置按钮,选择ssh配置文件 配置举例 ``` Host tencentcloud HostName ...
- NIST随机数测试软件安装与使用 && igamc:UNDERFLOW
https://csrc.nist.gov/ https://csrc.nist.gov/projects/random-bit-generation/documentation-and-softwa ...
- Linux命令学习-01——find -type
Linux find 命令用来在指定目录下查找文件. 任何位于参数之前的字符串都将被视为欲查找的目录名.如果使用该命令时,不设置任何参数,则 find 命令将在当前目录下查找子目录与文件.并且将查找到 ...
- gradle中的增量构建
目录 简介 增量构建 自定义inputs和outputs 运行时API 隐式依赖 输入校验 自定义缓存方法 输入归一化 其他使用技巧 gradle中的增量构建 简介 在我们使用的各种工具中,为了提升工 ...
- Github App version released icons
# Github App version released icons http://shields.io/ Build Travis: https://img.shields.io/travis/U ...
- js GC & stack heap
js GC & stack heap stack 栈,函数执行形成执行栈帧,变量名,指针 heap 堆,非结构化的数据(Object),分配的内存的存储空间 js 垃圾回收机制 https:/ ...
- CSS Shapes
CSS Shapes shape-outside & shape-image-threshold <img class="element" src="ima ...
- PHP实现一个二维码同时支持支付宝和微信支付
实现思路 生成一个二维码,加入要处理的url连接 在用户扫完码后,在对应的脚本中,判断扫码终端,调用相应的支付 若能够扫码之后能唤起相应app,支付宝要用手机网站支付方式,微信要使用jsapi支付方式 ...