算法题库(更新中)

KMP

1_模板考查

1.https://www.acwing.com/problem/content/833/

题解

题意

自己看

思路

本身就是KMP模板题,看不明白看以下博客

https://www.cnblogs.com/RimekeyBergling/p/16625360.html


#include<bits/stdc++.h>
using namespace std;
const int N = 1000010, M = 1000010;
int  n, m;
char p[N], s[M];
int nx[N];

int main()
{
	cin >> n >> p + 1 >> m >> s + 1;
	//预处理nx
	for (int i = 2, j = 0; i <= n; i++)
	{
		while (j && p[i] != p[j + 1])//当已经匹配到至少一个字符时,查看下一个字符是否匹配;
		{
			j = nx[j];//如果不匹配,就等于那个没有匹配成功的字符的nx;
		}
		if (p[i] == p[j + 1])//如果发现字符相同;
		{
			j++;//查看下一个字符
		}
		nx[i] = j;//储存nx[i];
	}


	//KMP匹配
	for (int i = 1, j = 0; i <= m; i++)//i枚举s , j枚举p;
	{
		while (j && s[i] != p[j + 1])//j没有推到终点就无法匹配;
		{
			j = nx[j];
		}
		if (s[i] == p[j + 1])//匹配成功
		{
			j++;//移动到下一位
		}
		if (j == n)//p完全与s的一部分字符匹配成功
		{
			cout << i - n << ' ';//匹配成功的下标;
			j = nx[j];//移动
		}
	}

	return 0;
}

————————————————————————————————————

2.http://poj.org/problem?id=3461

题解

题意

短字符能在长字符串里匹配几次;

思路

模板,改一下匹配成功后,记录数量即可;

#include<iostream>
#include<string>
using namespace std;
int main()
{
    int t;
    cin >> t;
    while(t--)
    {

        //题目没给字符串长度,这里用char不太好找字符串里有字符的长度,所以用了string;
        string p , s;
        int nx[1000006];
        cin >> p >> s;
        int n , m;
        int cnt = 0;
        n = p.size();
        m = s.size();

        //建议从下标1开始读入,所以直接再字符串p和s最前面拼接一个无关字符即可;
        p = '1' + p;
        s = '1' + s;

        //模板化的nx预处理
        for(int i = 2 , j = 0 ; i <= n ; i++)
        {
            while(j && p[i] != p[j + 1])
            {
                j = nx[j];
            }
            if(p[i] == p[j + 1])
            {
                j++;
            }
            nx[i] = j;
        }
        
        //同样是模板化的匹配,只是把匹配成功后的行为更改为计数,然后统一打印
        for(int i = 1 , j = 0 ; i <= m ; i++)
        {
            while(j && s[i] != p[j + 1])
            {
                j = nx[j];
            }
            if(s[i] == p[j + 1])
            {
                j++;
            }
            if(j == n)
            {
                cnt++;
                j = nx[j];
            }
        }
        
        cout << cnt << endl;
    }
    return 0;
}

————————————————————————————————————

2_next的使用

1.http://poj.org/problem?id=2752

题解

题意

输入一个字符串,判断这个字符串前缀和后缀是否相同,相同时输出相同部分字符串的长度。

思路

就拿第一个样例

ababcababababcabab ———————— 2 4 9 18

经过next处理后,会发现

原字符串
a b a b c a b a b a b a b c a b a b

next数组
0 0 1 2 0 1 2 3 4 3 4 3 4 5 6 7 8 9

首先原字符串就是个最长前后缀相同的名字,所以会有最长名字为原字符串长度 = 18;

其次找到下标为18的最后一个next[18],说明字符串前9个和后9个的后缀是相同的,所以会有next[18] = 9;

由于已经缩到了1~9的范围,所以依旧倒着找,next[9] = 4;

这次在1~4里找,next[4] = 2;

最后由于next[2] = 0 , 无法继续缩减范围,并且无法得到更多相同的前后缀;

所以对于该字符串,名字最多为2 4 9 18;

//以下代码在poj中请用G++提交,不然cin >> s会CE

#include<iostream>
#include<string.h>
using namespace std;
int main()
{
    int mem[400005];
    string s;
    while(cin >> s)
    {
        memset(mem , 0 ,sizeof mem);
        int next[400005];
        int n = s.size();
        s = '1' + s;
        
        for(int i = 2 , j = 0 ; i <= n ; i++)
        {
            while(j && s[i] != s[j + 1])
            {
                j = next[j];
            }
            if(s[i] == s[j + 1])
            {
                j++;
            }
            next[i] = j;
        }
        int tmp = n;
        while(next[tmp])
        {
            mem[next[tmp]]++;
            tmp = next[tmp];
        }
        
        for(int i = 1 ; i <= n ; i++)
        {
            if(mem[i] > 0)
            {
                cout << i << ' ';
            }
        }
        cout << n << endl;
    }
}

————————————————————————————————————

3_循环节

1.http://poj.org/problem?id=2406

题解

题意

个人感觉原题目的题意不太好理解;

就是求最小子串,然后找到字符串的循环节出现的次数;

思路

因为在kmp里,next表示———如果s第i位(设s[0]为第0位)与p第j位不匹配,则要回到第next[i]位继续与文本串第j位匹配。

那么模式串第1位到next[n]与模式串第n-next[n]位到n位是匹配的。

所以如果n%(n-next[n])== 0,则存在重复连续子串,而最小的字串长度为n-next[n]。

举例理解

原字符串
a b a b c a b a b a b a b c a b a b

next数组
0 0 1 2 0 1 2 3 4 3 4 3 4 5 6 7 8 9

该例子n = 18 , next[n] = 9 , 此时n % (n - next[n]) = 18 % 9 = 0,说明存在重复连续子串;

长度为n - next[n] = 9,次数为n / (n - next[n]) = 2;

#include<iostream>
#include<cstring>
#include<string.h>
using namespace std;
int main()
{
   //题目明文要求cin需要加速
   std::ios::sync_with_stdio(false);
   std::cin.tie(0); 
   std::cout.tie(0);
   string s;
    while(cin >> s)
    {
        //特判一下结尾的"."
        if(s == ".")
        {
            return 0;
        }
        //模板设next
        int next[1000006];
        int n;
        n = s.size();
        s = '1' + s;
        
        for(int i = 2 , j = 0 ; i <= n ; i++)
        {
            while(j && s[i] != s[j + 1])
            {
                j = next[j];
            }
            if(s[i] == s[j + 1])
            {
                j++;
            }
            next[i] = j;
        }

        //如果n%(n-next[n])== 0,则存在重复连续子串,而最小的字串长度为n-next[n]。
        //n / (n - next[n])则会得到数量
        if(n % (n - next[n]) == 0)
        {
            cout << n / ( n - next[n]) << endl;
        }
        else
        {
            cout << 1 << endl;
        }
    }
}

————————————————————————————————————

2.http://poj.org/problem?id=1961

题解

题意

一个字符串,求这个字符串到第i个字符为止的循环节的次数。

思路

跟上一题思路差不多,唯一不同的就是当字符串里到达第i个字符时,就把它的循环节打印出来;

#include<iostream>
#include<cstring>
using namespace std;
int main()
{
    int next[1000006];
    int cnt = 1;
    int n;
    cin >> n;
    while(1)
    {
        if(n == 0)
        {
            return 0;
        }
        string s;
        cin >> s;
        s = '1' + s;
        memset(next , 0 , sizeof (next));
        cout << "Test case #" << cnt << endl;
        cnt++;
        for(int i = 2 , j = 0 ; i <= n ; i++)
        {
            while(j && s[i] != s[j + 1])
            {
               j = next[j]; 
            }
            if(s[i] == s[j + 1])
            {
                j++;
            }
            next[i] = j;
        }
        
        
        
        for(int i = 2 ; i <= n ; i++)
        {
            if(i % (i - next[i]) == 0 && next[i] > 0)
            {
                cout << i << " " << i / (i - next[i]) << endl;
            }
        }
        cout << endl;
        cin >> n;
    }
    
    return 0;
}
posted @ 2022-09-13 20:33  Zilliax  阅读(62)  评论(0)    收藏  举报