首页 > 其他分享 >KMP AC自动机 Z函数

KMP AC自动机 Z函数

时间:2022-08-13 19:13:08浏览次数:59  
标签:AC 自动机 .. int res tr ++ KMP pi

KMP AC自动机 Z函数

\(s_{0..n-1}\)

前缀函数 \(\pi_i\)

最大的 \(k<i\)

使得 \(s_{0..k-1}=s_{i-k+1..i}\)

abcabcd

\(\pi_0=0\) 规定的

\(\pi_1=0\)

\(\pi_2=0\)

\(\pi_3=1\) abca 的公共前后缀 长度为 1

\(\pi_4=2\) abcab 的公共前后缀 ab 长度为 2

\(\pi_5=3\) abcabc -> abc

\(\pi_6=0\) abcabcd

如何暴力求

vector<int> findpi(string s) {
  vector<int> res;
  res.push_back(0);
  for (int i = 1; i < s.size(); ++i) {
    int t = 0;
    for (int j = 1; j < i; ++j) {
      if (s.substr(0, j) == s.substr(i - j, j))
      	t = j;
    }
    res.push_back(t);
  }
  return res;
}

优化1

\(\pi_{i}\le \pi_{i-1}+1\)

为什么都满足呢?

image-20220813085334107

每次枚举 \(\pi_{i}\) 时,从 \(\pi_{i-1}+1\) 开始从大向小枚举

vector<int> findpi(string s) {
  vector<int> res;
  res.push_back(0);
  for (int i = 1; i < s.size(); ++i) {
    int t = 0;
    for (int j = res[i - 1] + 1; j > 0; --j) {
      if (s.substr(0, j) == s.substr(i - j, j)) {
      	t = j;
      	break;
      }
    }
    res.push_back(t);
  }
  return res;
}

求 \(\pi_i\) 的时候 \(j\) 枚举的范围是 \([\pi_i,\pi_{i-1}+1]\) 所以要 \(\pi_{i-1}+1-\pi_i+1\) 次字符串比较

\[\sum_{i=1}^n(\pi_{i-1}+1-\pi_i+1)=2n+\pi_0-\pi_1+\pi_1-\pi_2+\pi_2-\cdots+\pi_{n-1}-\pi_n\\=2n+\pi_0-\pi_n=2n-\pi_n \]

所以需要 \(O(n)\) 次比较字符串(比较一次是 \(O(n)\))

优化2

image-20220813092419539
vector<int> findpi(string s) {
  vector<int> pi;
  pi.push_back(0);
  for (int i = 1; i < s.size(); ++i) {
    int j = pi[i - 1];
    while (j > 0 && s[j] != s[i]) j = pi[j - 1];
    // 两种情况
    // s[j] = s[i]: pi[i] = j + 1 
    // j = 0: pi[i] = 0
    if (s[j] == s[i]) pi.push_back(j + 1);
    else pi.push_back(0);
  }
  return pi;
}

\(O(n)\) 次比较字符

KMP

在大串 \(s\) 中寻找小串 \(p\)

对于 \(ps\) 求前缀函数 \(\pi\)

对于 \(s\) 中的位置 \(t\),它在 \(ps\) 中的位置是 \(|p|+t\)

只需要看 \(\pi_{|p|+t}\) 是否大于等于 \(|p|\) =>

p=abc

s=cabababcabc

ps = abc|cabababcabc
pi = 000|01212123123

p  = abc
ps = abc|abcabcabc
pi = 000|123456789
这么做是不对的。

p  = abc
p#s= abc#abcabcabc
pi = 0000123123123

对于 \(p\#s\) 中的 \(s\),它的 \(\pi\) 是不会超过 \(|p|\)。

vector<int> kmp(string s, string p) {
  vector<int> pi = findpi(p);
  // 
}

一些应用

统计每个前缀 \(s_{0..i}\) 在 \(s\) 的出现次数

考虑前缀 \(s_{0..k}\)

假设对于 \(s_{0..\pi_i-1}\) 而言,它有一个后缀等于 \(s_{0..k}\)

那么 \(s_{0..i}\) 也有一个后缀等于 \(s_{0..k}\)

如果 \(s_{0..i}\) 有一个后缀等于 \(s_{0..k}\),那么 \(s_{0..\pi_{i}-1}\) 也有

\(s_{0..i}\) 的每一次出现,都代表了一次 \(s_{0..\pi_{i}-1}\) 的出现

有几个本质不同的子串

位置不同,内容相同是本质相同的子串

求 \(s+c\) 会比 \(s\) 多几个本质不同的子串

\(s+c\) 中新产生的本质不同的子串一定是后缀。

如果一个后缀被 \(s\) 包含了,那么比它短的后缀一定也被 \(s\) 包含了

最长的被 \(s\) 包含的后缀

把 \(s+c\) 倒过来,

如果一个前缀被 \(s\) 包含

\(O(n^2)\)

给一个串 和 \(q\) 个询问,每个询问是某个区间的本质不同的子串个数

AC 自动机

int tr[N][26], cnt, leaf[N];

void insert(char *s) {
  int n = strlen(s);
  int v = 0;
  for (int i = 0; i < n; ++i) {
    if (!tr[v][s[i] - 'a'])
      tr[v][s[i]-'a'] = ++cnt;
   	v = tr[v][s[i] - 'a'];
  }
  leaf[v] = 1; // 是字符串的末尾
}

// 怎么求 fail

void build() {
	// 假设已经求好了 tr 数组
  queue<int> q;
  fail[0] = 0;
  for (int c = 0; c < 26; ++c) {
    if (tr[0][c]) {
	    fail[tr[0][c]] = 0;
  		q.push(tr[0][c]);
    }
  }
  while (!q.empty()) {
    int h = q.front();
    q.pop();
    for (int c = 0; c < 26; ++ c) {
      if (tr[h][c]) {
        fail[tr[h][c]] = tr[fail[h]][c];
      	q.push(tr[h][c]);
      }
      else
        tr[h][c] = tr[fail[h]][c];
 	  }
  }
}

int query(char *s) // s 中出现了几次 p
  // p1 = aa p2 = bb, aabbaa 3
{
  int u = 0, res= 0, n = strlen(s);
  for (int i = 0; i < n; ++i) {
    u = tr[u][s[i] - 'a'];
    for (int j = u; j && leaf[j]; j = fail[j]) {
      res += leaf[j];
      leaf[j] = 0;
    }
  }
  return res;
}

标签:AC,自动机,..,int,res,tr,++,KMP,pi
From: https://www.cnblogs.com/swtsun2009/p/16583825.html

相关文章

  • acwing 1228. 油漆面积 扫描线
     X星球的一批考古机器人正在一片废墟上考古。该区域的地面坚硬如石、平整如镜。管理人员为方便,建立了标准的直角坐标系。每个机器人都各有特长、身怀绝技。它们感兴......
  • Macbook Pro苹果电脑快捷键 + 使用技巧
    好记性不如烂笔头 第一次使用MacbookPro,整理一些使用技巧,以免忘记。 快捷方式复制:command+c粘贴:command+v复制出一个副本,相当于原地复制粘贴:command+d剪切:comma......
  • accept的文件类型
    *.3gppaudio/3gpp,video/3gpp3GPPAudio/Video*.ac3audio/ac3AC3Audio*.asfallpication/vnd.ms-asfAdvancedStreamingFormat*.auaudio/basicAUAu......
  • React生命周期和响应式原理(Fiber架构)
    注意:只有类组件才有生命周期钩子函数,函数组件没有生命周期钩子函数。生命周期装载阶段:constructor()render()componentDidMount()更新阶段:render()compone......
  • 京东新买的MacBook Pro安装Homebrew教程
    想买MacBookPro很久了,前天晚上突然痛下决心,由于我已经毕业4年,要想享受教育优惠的话,苹果官网很难认证通过。所以决定在京东自营店购买,京东自营店认证教育优惠只需在学信网......
  • webpack使用
    Node.js遵循CommonJs规范,核心思想允许require方法来同步加载所需依赖的其他模块,通过exports或者module.exports来导出需要暴露的接口 优点:服务端模块便于复用缺点:同步......
  • mac m1 使用UTM安装win10
    安装win虚拟机因为合作厂商有个客户端是他们自研的,他们只有win版本。   工具: mac:14寸m1芯片, macOSMonterey12.3UTM:3.2.4版本  最新版:https:/......
  • OpenWrt之package: Using Dependencies
    目录OpenWrt之package:UsingDependencies前言总览/Topic依赖类型/Dependencytypes特别说明/SpecialNotes警告/Caveats使用bool运算符/Usingbooleanoperato......
  • UVA10089 Repackaging
    设第\(i\)种包装中\(j\)尺寸的杯子数量为\(S_{ij}\),第\(i\)种包装选取的数量为\(a_i\),若能够按照要求进行重新打包,有\[\begin{cases}a_1S_{11}+a_2S_{21}......
  • 解决 MAUI 在mac上编译提示 The path 'XXXXXXX\Shared\MainLayout.razor.css' would
    路径'XXXXXXX\Shared\MainLayout.razor.css'将导致应用程序包之外的文件并且无法使用DescriptionTheerrorhappenswithBlazorMAUIHybridProject.Projectcompil......