bzoj 3145 - [Feyat cup 1.5]Str

Description

给你两个长度\(10^5\)级别的串\(S, T\)

求\(S,T\)的最长模糊匹配公共子串

模糊匹配 : 至多一个位置上的字符不同

Analysis

屯了好久的题, 之前看过3次都看不懂, 一直觉得这道题非常恐怖

现在重新翻出来看很快就有思路了, 也不是很恐怖嘛 (写了一个下午系列)

定义模糊点 : 位置不同的字符

情况1. 模糊点均在两串的中间部分

那么我们可以分为\({A + * + B}\) , 其中\(*\)为模糊点

一个想法是对\(S\)建自动机, 然后从左往右扫\(T\)串, 在\(S\)中得到\(A\)部分的最大匹配的状态

然后那个状态有个\(right\)集, \(right + 2\)就是\(B\)部分的开头

我们用后缀数组来匹配\(B\)部分, 根据贪心, rk一定会是相邻的

我们在\(SAM\)(前缀逆序树)上维护两棵平衡树 , 一颗存出现在\(S\)串中的那些rk, 一颗存\(T\)的, 启发式合并的时候两两找相邻点在\(SA\)里求\(LCP\)

后来想了以下发现不行, 我这样\(A\)部分相当于贪心的找了最长的, 但是有可能该状态往上跳几下得到一对更优的\(B\)呢

我们发现\(S,T\)建广义自动机, 这样每个状态对应了一种长度的\(A\)部分公共子串

然后照样做就好了

Claris的方法是用反串的后缀数组替代广义自动机, 然后枚举公共\(A\)部分长度, 在rk上合并相邻\(lcp >= len\)的点

并查集 + 启发式合并

情况2. 至少一个模糊点在串头/串尾

在实现过程中特判处理即可

串头的用 后缀数组 匹配

串尾的用 前缀逆序树 匹配

Code

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <cmath>
#include <algorithm>
#include <set>
#define rep(i,a,b) for (int i = (a); i <= (b); ++ i )
#define per(i,a,b) for (int i = (a); i >= (b); -- i )
#define For(i,a,b) for (int i = (a); i < (b); ++ i )
#define fore(i,a) for (its it = (a).begin(); it != (a).end(); ++ it )
using namespace std;
const int M = 2e5 + 7;
const int INF = 1e9 + 7; char s[M], t[M];
int ls, lt;
int ans = 1; namespace SA{
static const int N = M << 1;
char s[N]; int n, mid;
int sa[N], t[N];
int rk[N], f[N];
int st[N][20], ln[N];
int h[N]; void getsa(){
static int sum[M];
int p, nw, i, j;
for (i=1; i<=n; ++i) sum[s[i]]++;
for (i=0; i<=255; ++i) sum[i] += sum[i-1];
for (i=1; i<=n; ++i) sa[sum[s[i]]--] = i;
for (p=0, i=1; i<=n; ++i) rk[sa[i]] = (p += (s[sa[i]] != s[sa[i-1]]));
for (nw=p, j=1; nw!=n; j<<=1, nw=p) {
memset(sum, 0, (1+nw)<<2);
memcpy(f, rk, sizeof rk);
for (p=0, i=n-j+1; i<=n; ++i) t[++p] = i;
for (i=1; i<=n; ++i) if (sa[i] > j) t[++p] = sa[i] - j;
for (i=1; i<=n; ++i) sum[f[i]]++;
for (i=1; i<=nw; ++i) sum[i] += sum[i-1];
for (i=n; i>=1; --i) sa[sum[f[t[i]]]--] = t[i];
for (p=0, i=1; i<=n; ++i) rk[sa[i]] = (p += (f[sa[i]] != f[sa[i-1]] || f[sa[i]+j] != f[sa[i-1]+j]));
}
} void geth(){
int i, j, p;
for (p=0, i=1; i<=n; ++i){
j = sa[rk[i]-1];
for (; s[i+p] == s[j+p]; ++p);
h[rk[i]] = p;
if (p > 0) --p;
}
} void init(){
rep (i, 1, n) st[i][0] = h[i];
int i; for (ln[0]=ln[1]=0, i=2; i<=n; ++i) ln[i] = ln[i>>1] + 1;
per (i, n, 1)
rep (j, 1, ln[n-i+1])
st[i][j] = min(st[i][j-1], st[i + (1 << j-1)][j-1]);
} inline int LCP(int x, int y){
if (x == y) return n-y+1;
if (x > y) swap(x, y);
++x; int l = ln[y-x+1];
return min(st[x][l], st[y - (1 << l) + 1][l]);
} void build(char *a, char *b, int la, int lb){
n = 0;
rep (i, 1, la) s[++n] = a[i];
s[mid = ++n] = '+';
rep (i, 1, lb) s[++n] = b[i]; getsa();
geth();
init();
}
} namespace Set{
static const int N = M << 1;
int ans[N];
int rt[2][N];
set<int> s[2][N];
typedef set<int> :: iterator its; int find(int x, int k, int p){
int res = -1;
its rt = s[k][x].upper_bound(p);
if (rt != s[k][x].end())
res = max(res, SA::LCP(*rt, p));
if (rt != s[k][x].begin())
res = max(res, SA::LCP(*(--rt), p));
return res;
} void ins(int x, int k, int p){
ans[x] = max(ans[x], find(x, k^1, p));
s[k][x].insert(p);
} void merge(int &x, int y, int k){
if (s[k][x].size() < s[k][y].size()) {
fore (it, s[k][x]) s[k][y].insert(*it);
x = y;
}
else
fore (it, s[k][y]) s[k][x].insert(*it);
} int upd(int x, int y, int kx, int ky){
int res = -1;
if (s[kx][x].size() < s[ky][y].size())
fore (it, s[kx][x]) res = max(res, find(y, ky, *it));
else
fore (it, s[ky][y]) res = max(res, find(x, kx, *it));
return res;
} void Merge(int x, int y){
rep (i, 0, 1) ans[x] = max(ans[x], upd(rt[i][x], rt[i^1][y], i, i^1));
rep (i, 0, 1) merge(rt[i][x], rt[i][y], i);
ans[x] = max(ans[x], ans[y]);
}
} namespace SAM{
static const int N = M << 1;
int last, tot;
int ch[N][26];
int stp[N], fa[N];
int rt[2][N], ed[2]; inline int newnode(int l){
stp[++tot] = l;
rt[0][tot] = rt[1][tot] = INF;
return tot;
} int ext(int p, int q, int c){
int nq = newnode(stp[p] + 1);
fa[nq] = fa[q], fa[q] = nq;
memcpy(ch[nq], ch[q], sizeof ch[q]);
for (; p && ch[p][c] == q; p = fa[p]) ch[p][c] = nq;
return nq;
} int sam(int p, int c){
int np = ch[p][c];
if (np) return (stp[p] + 1 == stp[np]) ? np : ext(p, np, c); np = newnode(stp[p] + 1);
for (; p && ch[p][c] == 0; p = fa[p]) ch[p][c] =np;
if (!p) fa[np] = 1;
else {
int q = ch[p][c];
fa[np] = (stp[p] + 1 == stp[q]) ? q : ext(p, q, c);
}
return np;
} void trans(int &p, int c, int &len){
if (!ch[p][c]) {
for (; p && ch[p][c] == 0; p = fa[p]);
len = stp[p];
}
if (ch[p][c]) p = ch[p][c], ++len;
else p = 1, len = 0;
} void build(char *a, char *b, int la, int lb){
last = tot = 1;
rep (i, 1, la)
rt[0][ last = sam(last, a[i]-'a') ] = i;
last = 1;
rep (i, 1, lb)
rt[1][ last = sam(last, b[i]-'a') ] = la + 1 + i; ed[0] = la, ed[1] = la + 1 + lb;
rt[0][1] = 0;
rt[1][1] = la + 1;
} void topu(){
static int sum[N], pid[N];
rep (i, 1, tot) sum[stp[i]]++;
rep (i, 1, tot) sum[i] += sum[i-1];
rep (i, 1, tot) pid[sum[stp[i]]--] = i;
rep (i, 1, tot) rep (j, 0, 1) {
Set::rt[j][i] = i;
Set::ans[i] = -1;
if (rt[j][i] != INF && rt[j][i]+2 <= ed[j])
Set::ins(i, j, SA::rk[rt[j][i] + 2]);
}
per (i, tot, 1) {
int x = pid[i];
if (rt[0][x] != INF && rt[1][x] != INF)
ans = max(ans, stp[x] + 1 + Set::ans[x]),
ans = max(ans, stp[x] + (rt[0][x] < ed[0] && rt[1][x] < ed[1]) );
if (x != 1){
rep (j, 0, 1)
rt[j][fa[x]] = min(rt[j][fa[x]], rt[j][x]);
Set::Merge(fa[x], x);
}
}
}
} int main(){
#ifndef ONLINE_JUDGE
freopen("a.in", "r", stdin);
#endif
int i; scanf("%s", s+1); ls = strlen(s+1);
scanf("%s", t+1); lt = strlen(t+1); SAM::build(s, t, ls, lt);
SA::build(s, t, ls, lt);
SAM::topu(); printf("%d\n", ans); return 0;
}

Bzoj 3145 - [Feyat cup 1.5]Str的更多相关文章

  1. [BZOJ 3145][Feyat cup 1.5]Str 解题报告

    [Feyat cup 1.5]Str DescriptionArcueid,白姬,真祖的公主.在和推倒贵看电影时突然对一个问题产生了兴趣:我们都知道真祖和死徒是有类似的地方.那么从现代科学的角度如何解 ...

  2. BZOJ3145 [Feyat cup 1.5]Str 后缀树、启发式合并

    传送门--BZOJCH 考虑两种情况: 1.答案由一个最长公共子串+可能的一个模糊匹配位置组成.这个用SAM求一下最长公共子串,但是需要注意只出现在\(S\)的开头和\(T\)的结尾的子串是不能够通过 ...

  3. BZOJ3145 : [Feyat cup 1.5]Str

    如果不存在模糊点,那么答案就是两个串的最长公共子串. 如果模糊点是某个串的开头或者结尾,那么可以暴力枚举另一个串中的某个前后缀更新答案. 否则,假设模糊点在第一个串里是$i$,在第二个串里是$j$,那 ...

  4. bzoj AC倒序

    Search GO 说明:输入题号直接进入相应题目,如需搜索含数字的题目,请在关键词前加单引号 Problem ID Title Source AC Submit Y 1000 A+B Problem ...

  5. Sam做题记录

    Sam做题记录 Hihocoder 后缀自动机二·重复旋律5 求一个串中本质不同的子串数 显然,答案是 \(\sum len[i]-len[fa[i]]\) Hihocoder 后缀自动机三·重复旋律 ...

  6. Python数据分析实例操作

    import pandas as pd #导入pandas import matplotlib.pyplot as plt #导入matplotlib from pylab import * mpl. ...

  7. eval(PHP 4, PHP 5)

    eval — 把字符串作为PHP代码执行 说明 mixed eval ( string $code_str ) 把字符串 code_str 作为PHP代码执行. 除了其他,该函数能够执行储存于数据库文 ...

  8. PHP 一句话木马

    eval 函数 eval() 函数把字符串按照 PHP 代码来计算 该字符串必须是合法的 PHP 代码,且必须以分号结尾 如果没有在代码字符串中调用 return 语句,则返回 NULL.如果代码中存 ...

  9. bzoj 5337 [TJOI2018] str

    bzoj 5337 [TJOI2018] str Link Solution 水题 直接 \(f[i][j]\) 表示以第 \(i\) 位为结束位置,当前已经匹配了前 \(j\) 个氨基酸的方案数 使 ...

随机推荐

  1. vim正则表达式的替换变量

    在正规表达式中使用 \( 和 \) 符号括起正规表达式,即可在后面使用\1.\2 等变量来访问 \( 和 \) 中的内容. 例如有下列英汉对照文本: adapter 适配器address 地址alge ...

  2. Nginx http重定向到https时,post请求变为get的问题

    Http协议是有状态码的,比较熟悉的状态码是200,代表链接OK,还有404和403,一个是not found,另一个是forbidden.Not found不用多说,可能是地址栏输入有误,或者是网页 ...

  3. jsp中的文件上传

    首先需要有以下的jar包 jsp代码如下: <!-- ${pageContext.request.contextPath}为: "/" + 当前项目名 --> < ...

  4. 五 python并发编程之IO模型

    一 IO模型介绍 同步(synchronous) IO和异步(asynchronous) IO,阻塞(blocking) IO和非阻塞(non-blocking)IO分别是什么,到底有什么区别?这个问 ...

  5. java中的继承 (2013-10-11-163 写的日志迁移

    继承:为了解决代码重用 定义: 子类通过继承父类,可以调用父类中非私有的属性和方法,达到重用的目的,通过关键字extends实现:   ################以下为代码演示: class A ...

  6. <Docker学习>1. 简介

    Q: Dokcer是什么? A: 是一种虚拟化技术.参考https://www.imooc.com/learn/867快速了解Docker. Q: 传统虚拟机技术和Dokcer的区别? A: 传统虚拟 ...

  7. vue之神奇的动态按钮

    今天我们将利用vue的条件指令来完成一个简易的动态变色功能按钮 首先我们还是要对vue进行配置,在上篇随笔中有相关下载教学. 然后我们要在html页面上搭建三个简易的按钮,颜色分别为紫,绿和蓝(颜色随 ...

  8. 常用模块之 re shutil configparser hashlib xldt和xlwd

    shutil 高级文件处理模块 封装的更简单了 主要是文件的复制,移动,压缩解压缩 需要保证目标文件已经存在shutil.copymode('test.txt','testcopy4.txt') 压缩 ...

  9. systick运用

    systick的原理前一篇博文有介绍,简而言之就是SysTick定时器是一个24位的倒计数,当倒计数为0时,将从RELOAD寄存器中取值作为定时器的初始值,同时可以选择在这个时候产生中断(异常号:15 ...

  10. 记忆化搜索:POJ1088-滑雪(经典的记忆化搜索)

    skiing 时间限制:3000 ms  |  内存限制:65535 KB 难度:5 描述 Michael喜欢滑雪百这并不奇怪, 因为滑雪的确很刺激.可是为了获得速度,滑的区域必须向下倾斜,而且当你滑 ...