【笔记】字符串基础

输入&输出

一般的东西就不说了

  1. 先讲读入一整行

    • cin

      std :: cin.getline(s,max_length);
      

      其实 cin cout 这两个类里面有好多好东西。

    • scanf

      scanf("%[^\n]s",s);//其实 scanf("%[^\n]",s); 也行就是了
      

      我之前完全不知道 scanf 可以这么用,总之 [] 里面是允许(加 ^ 是不允许)读入的字符集。

    • getsfgets

      很简单粗暴的用法

      gets(s);
      fgets(s,max_length,stdin);
      

      我看完这个之后才理解 stdin 也能算是(存疑)一个文件指针?

  2. 特化字符串 STL:string

字符串匹配

KMP

前缀数组:

我们记字符串 \(s[N]\) 的前缀为 \(s_i\),并记 \(s_i\) 的前缀为 \(pre_{i,j}\),后缀为 \(suf_{i,j}\)

前缀数组的定义就是:

\[\pi[i] = \max_{\substack{1 \le j\le i-1\\ pre_{i,j} = suf_{i,j}}}j \]

即前/后缀的最大重合长度。

这个东西暴力就是 \(\cal{O}(n^2)\) 的。

但若已知 \(\pi[0] \sim \pi[k-1]\),是否有更好的办法求 \(\pi[k]\) 呢?

  1. 如果接着 \(k-1\) 位的最大前/后缀,他们后面的字符是匹配的,那么直接 \(\pi[k] \gets \pi[k-1]+1\) 就好;

  2. 如果不能匹配,我们也要找一个 \(k-1\) 位时的次大的能够匹配前/后缀,再看看他们后面的字符是否匹配。

那次大的前/后缀,不就是最大的前/后缀的最大前/后缀嘛,直接用 \(\pi[\pi[k-1]]\) 就好了!

举一个例子看看:

abacabab

当到最后一个 b 的时候,显然之前的最大前/后缀 aba 加上后面的字符后就变成了 abacabab,不相匹配。那我们找次大前后缀,即 aba 的最大前/后缀 a,发现 ab 是相互匹配的,于是 \(\pi[7] = 2\)

代码环节:

const int N = 100010;
int prefix[N];
char s[N];
void get_prefix() {
	prefix[0] = -1;
	for(int i = 1, j = -1;s[i];++i) {
		while(s[i] != s[j+1]&&j >= 0) 
			j = prefix[j];
		if(s[i] == s[j+1]) ++j;
		prefix[i] = j;
	}
}

这里采用的 \(\pi[i]\) 其实是上文中 \(\pi[i]-1\)

复杂度分析:

其他都比较明了,主要是 while 段为什么最后会是 \(\cal{O}(n)\) 的。因为 \(j\) 在每次循环时最多只能自增一次,而每次跳 \(j \gets \pi[j]\)\(j\) 至少减少 \(1\),故此环节只会执行 \(\cal{O}(n)\) 次。

KMP:

问字符串 \(pattern[N]\)\(text[M]\) 中出现了多少次。

很简单,把两个字符串放一起,中间加一个分隔符,求一遍前缀数组,看看有多少个 \(\pi[i] = N\) 就好了。

Z 函数

AC 自动机

刚才 KMP 解决的是单个模式串的情况,当我们有多个模式串的时候,是否可以有类似于前缀数组一样的东西呢?

实际上是可以的,只不过原来是一个串,现在是一棵 trie 树。原来的前缀数组现在我们叫他失配指针,算失配指针的思路也是类似的。

posted @ 2024-12-17 14:19  noaL02d  阅读(12)  评论(0)    收藏  举报