重学字符串全家桶

为什么要重学?因为我全忘了。希望我不会再忘掉。

KMP

主要在于 nxt 数组。\(nxt_i\) 表示的是其第 \(i\) 个前缀中,前缀等于后缀且长度不是 \(i\) 的最大长度。这样我们在匹配的过程中,如果失败,就直接跳 nxt 指针,直到配上。时间复杂度 \(O(n)\),我不会证。

代码:

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
string s1,s2;
int n,m,nxt[1000005];
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	cin>>s1>>s2;
	n=s1.length(),m=s2.length();
	int j=0;
	for(int i=1;i<m;i++)
	{
		while(j&&s2[j]!=s2[i])j=nxt[j-1];
		if(s2[j]==s2[i])++j;
		nxt[i]=j;
	}
	j=0;
	for(int i=0;i<=n;i++)
	{
		while(j&&s2[j]!=s1[i])j=nxt[j-1];
		if(s1[i]==s2[j])j++;
		if(j==m)cout<<i-(m-1)+1<<endl;
	}
	for(int i=0;i<m;i++)cout<<nxt[i]<<" ";
	cout<<endl;
	return 0;
}

用途:判循环、求最小循环节

结论:一个循环的长度为 \(n\) 的字符串的最小循环节是 \(n-nxt_n\)。感性理解就行了。判循环同理。

例题:AT_arc060_d Best Representation

如果一个字符串自己就不循环,答案是显然的;如果这个字符串是由一个字符连续坠起来的,答案也是显然的。否则可以证明分成两个是可以的,于是枚举断点,对前后两个分别判循环。

#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
#define endl "\n"
#define int long long
using namespace std;
const int N = 500005;
char s[N], ss[N];
int n, nxt[N], nxtr[N], cnt;
bool flag = true;
signed main()
{
    scanf("%s", s + 1);
    n = strlen(s + 1);
    for (int i = 2; i <= n; i++)
        if (s[i] != s[1]) flag = false;
    if (flag) return cout << n << "\n" << 1, 0;
    for (int i = 2, j = 0; i <= n; i++)
    {
        while (j && s[i] != s[j + 1]) j = nxt[j];
        if (s[i] == s[j + 1]) j++;
        nxt[i] = j;
    }
    for (int i = 1; i <= n; i++) ss[i] = s[n - i + 1];
    for (int i = 2, j = 0; i <= n; i++)
    {
        while (j && ss[i] != ss[j + 1]) j = nxtr[j];
        if (ss[i] == ss[j + 1]) j++;
        nxtr[i] = j;
    }
    if (nxt[n] == 0 || n % (n - nxt[n])) return cout << 1 << "\n" << 1, 0;
    cout << 2 << "\n";
    for (int i = 1; i < n; i++)
        if ((nxt[i] == 0 || i % (i - nxt[i])) &&
            (nxtr[n - i] == 0 || (n - i) % (n - i - nxtr[n - i])))
            cnt++;
    cout << cnt << "\n";
    return 0;
}
posted @ 2024-04-13 21:10  lhc0707  阅读(18)  评论(0编辑  收藏  举报