KMP算法

字符串

HDU 2087 剪花布条

一块花布条,里面有些图案,另有一块直接可用的小饰条,里面也有一些图案。对于给定的花布条和小饰条,计算一下能从花布条中尽可能剪出几块小饰条来呢?
Sample Input
abcde a3
aaaaaa aa

Sample Output
0
3

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
char a[1000100],b[1000100];
int p[1000100];
int main()
{
    while(scanf("%s",a+1)&& a[1] != '#'&&scanf("%s",b+1)){	
        memset(p,0,sizeof(p));//a为字符串,b为模式串 
        int la=strlen(a+1),lb=strlen(b+1);
        int count=0,j=0;
        p[1]=0;
        //先处理出p数组,无非是b和自己匹配,与b和a匹配一样,故代码差不多
        for(int i=2;i<=lb;i++)
        {
            while(j>0 && b[i]!=b[j+1]) j=p[j];//往前翻记录了有相同前缀的j
            if(b[i]==b[j+1]) j++;//i匹配成功了,i继续往后
            p[i]=j;
        }
        j=0;
        //两个字符串匹配 
        for(int i=1;i<=la;i++)
        {
            while(j>0 && a[i]!=b[j+1]) j=p[j];
            if(a[i]==b[j+1]) j++;
            if(j==lb) 
		    count++,j=0; 
        }
        printf("%d\n",count);
        }
		return 0;
    }

HDU 1686 Oulipo

典型的KMP板子题(同 HDU 1711 Number Sequence)

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
char a[1000100],b[1000100];
int p[1000100];
int main()
{   int t;
    scanf("%d",&t);
    while(t--){	
        scanf("%s%s",b+1,a+1);
        memset(p,0,sizeof(p));//a为字符串,b为模式串 
        int la=strlen(a+1),lb=strlen(b+1);
        int count=0,j=0;
        p[1]=0;
        //先处理出p数组,无非是b和自己匹配,与b和a匹配一样,故代码差不多
        for(int i=2;i<=lb;i++)
        {
            while(j>0 && b[i]!=b[j+1]) j=p[j];//往前翻记录了有相同前缀的j
            if(b[i]==b[j+1]) j++;//i匹配成功了,i继续往后
            p[i]=j;
        }
        j=0;
        //两个字符串匹配 
        for(int i=1;i<=la;i++)
        {
            while(j>0 && a[i]!=b[j+1]) j=p[j];
            if(a[i]==b[j+1]) j++;
            if(j==lb) 
		    count++,j=p[j]; //从下一个模式串 
        }
        printf("%d\n",count);
        }
		return 0;
    }

HDU 1251 前缀统计

方法一:用string和map解决

#include<stdio.h>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<map>
#include<string.h>
using namespace std;
map<string,int>pre; 

int main(){
	char a[12];
	while(gets(a)){
		if(a[0]=='\0')//或者写成==NULL 
		break;
		string str=a;//用字符串来存储字符数组a 
		for(int i=1;i<=str.size();i++){
			string s=str.substr(0,i);//在string中使用 
			pre[s]++;
		}
		/*
	    string s="";
        for(int i=0;a[i];i++)
        {
            s+=a[i];//这样写也可以 
            pre[s]++;
        }
		*/ 
	}
	while(gets(a))
    {
        string s=a;
        printf("%d\n",pre[s]);
    }
	return 0;
} 

方法二:采用二维数组用字典树来解决

#include <iostream>
#include <stdio.h>
using namespace std;

int trie[1000010][26];    //数组形式定义字典树,值存储的是下一个字符的位置
int num[1000010]={0};    //附加值,以某一字符串为前缀的单词的数量
int pos = 1;

void Insert(char word[])    //在字典树中插入某个单词
{
    int i;
    int c = 0;
    for(i=0;word[i];i++){
        int n = word[i]-'a';
        if(trie[c][n]==0)    //如果对应字符还没有值
            trie[c][n] = pos++;
        c = trie[c][n];
        num[c]++;
    }
}
int Find(char word[])    //返回以某个字符串为前缀的单词的数量
{
    int i;
    int c = 0;
    for(i=0;word[i];i++){
        int n = word[i]-'a';
        if(trie[c][n]==0)
            return 0;
        c = trie[c][n];
    }
    return num[c];
}

int main()
{
    char word[11];
    while(gets(word)){
        if(word[0]==NULL)    //空行。gets读入的回车符会自动转换为NULL。
            break;
        Insert(word);
    }
    while(gets(word))
        printf("%d\n",Find(word));
    return 0;
}
/*  HDU1251 与前缀统计差不多,注意格式 
banana
band
bee
absolute
acm

ba
b
band
abc 
*/

POJ 2503 Babelfish

本题主要考察的是字符串的输入,题目是要通过词义检索单词。
方法一:用map<string,string>进行查找

#include <iostream>
#include <string>
#include <map>
#include<stdio.h>
using namespace std;
map <string,string>mp;
int main ()
{
    char ch[30],str1[15],str2[15];
    while (gets(ch))
    {
        if (ch[0]==NULL)
            break;
        sscanf(ch,"%s%s",str1,str2);
        mp[str2]=str1;
    }
    while (gets(ch))
    {
        map<string,string>::iterator it; 
        it=mp.find(ch);//查找 
        if (it!=mp.end())
            cout<<(*it).second<<endl;
        else
            printf("eh\n");
    }
    return 0;
}

方法二:通过对字符串排序二分查找答案

#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
struct node
{
    char s1[20],s2[20];
} a[100005];
int cmp(node a,node b)
{
    return strcmp(a.s2,b.s2)<0;
	// 若字符串1>字符串2 则返回1,
	//字符串1<字符串2 则返回 -1,相等返回0。
}
int main()
{
    int i,j,low,mid,high,flag=1,len=0;
    char s[50];
    while(gets(s))
    {
        //当是空行时,字符串的长度是零;或者字符串第一位是'\0'
        //if(s[0]=='\0')
        if(strlen(s)==0)
        break;
        //sscanf可以从s中读取两个字符串
        sscanf(s,"%s%s",a[len].s1,a[len].s2);
        len++;
    }
    sort(a,a+len,cmp);//按字典序排列所以字符串
    //二分查找字符串
    while(gets(s))
    {
        low=0;
        high=len-1;
        flag=1;
        while(low<=high)
        {
            int mid =(low+high)/2;
            if(strcmp(s,a[mid].s2)==0)
            {
                printf("%s\n",a[mid].s1);
                flag = 0;
                break;
            }
            else if(strcmp(s,a[mid].s2)>0)
                low=mid+1;
            else
                high=mid-1;
        }
        if(flag==1)
            printf("eh\n");
    }
    return 0;
}

CFGym 101466E Text Editor

题目链接: link.
题意:已知A串,B串,和整数k,求B的最长前缀,此前缀在A串中出现的次数大于等于k次
这里很容易想到二分,但一个一个去枚举判断答案(如下)会超出时间

bool check(char s[],char t[],int mid,int k){
	str2=s;
	str1="";
	for(int i=0;i<mid;i++)
	str1=str1+t[i];
	for(int i=0;i<str2.size()-mid+1;i++){
            str=str2.substr(i,mid);
			if(str==str1)
            mp[str1]++;
            if(mp[str1]>=k){
            	mp.clear();
                return 1;
            }
        }
    mp.clear();
	return 0;
}

因此我们联想到KMP算法,代码如下:

#include<stdio.h>
#include<algorithm>
#include<map>
#include<cstring>
using namespace std;
string str,str1,str2;
char s[100005];
char t[100005];
int k,l,r,mid;
map<string,int>mp;//map映射
bool check(int mid,int k){
        int p[100005]={0};
        memset(p,0,sizeof(p));//a为字符串,b为模式串 
        int la=strlen(s+1),lb=mid;
        int count=0,j=0;
        p[1]=0;
        //先处理出p数组,无非是b和自己匹配,与b和a匹配一样,故代码差不多
        for(int i=2;i<=lb;i++)
        {
            while(j>0 && t[i]!=t[j+1]) j=p[j];//往前翻记录了有相同前缀的j
            if(t[i]==t[j+1]) j++;//i匹配成功了,i继续往后
            p[i]=j;
        }
        j=0;
        //两个字符串匹配 
        for(int i=1;i<=la;i++)
        {
            while(j>0 && s[i]!=t[j+1]) j=p[j];
            if(s[i]==t[j+1]) j++;
            if(j==lb) 
		    count++,j=p[j]; //从下一个模式串 
        }
//        printf("la %d  lb %d\n",la,lb);
//        printf("%d\n",count);
        if(count>=k)
        return 1;
        else 
        return 0;
}
int main(){
    gets(s+1);
    gets(t+1);
    scanf("%d",&k);
    l=0,r=strlen(t+1);

    bool flag=0;
    while(l<r){
    	mid=(l+r+1)/2;
    	if(check(mid,k)){
    		flag=1;
    		l=mid;
		}
    	else
    	r=mid-1;
	}
	if(flag==1){
		for(int i=1;i<=l;i++)
		printf("%c",t[i]);
	}
	else
	printf("IMPOSSIBLE\n");
	return 0;
}

UVA 11475 Extend to Palindrome

题意:求字符串末尾最少添加多少个字符可以把他变成回文串,输出这个回文串,很明显只要用KMP[判断一下反串和字符串最多能匹配到哪个位置,结尾加上反串这个位置之后的所有字符就可以了。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
char a[1000100],b[1000100];
int p[1000100];
int main()
{   
    while(gets(a+1)){	
        for(int i=1;i<=strlen(a+1);i++)
        b[strlen(a+1)-i+1]=a[i];
        memset(p,0,sizeof(p));//a为字符串,b为模式串 
        int la=strlen(a+1),lb=la;
        int count=0,j=0;
        p[1]=0;
        //先处理出p数组,无非是b和自己匹配,与b和a匹配一样,故代码差不多
        for(int i=2;i<=lb;i++)
        {
            while(j>0 && b[i]!=b[j+1]) j=p[j];//往前翻记录了有相同前缀的j
            if(b[i]==b[j+1]) j++;//i匹配成功了,i继续往后
            p[i]=j;
        }
        j=0;
        //两个字符串匹配 
        for(int i=1;i<=la;i++)
        {
            while(j>0 && a[i]!=b[j+1]) j=p[j];
            if(a[i]==b[j+1]) j++;//j为最多可以匹配到的位置 
//            if(j==lb) 
//		    count++,j=p[j]; //从下一个模式串 
        }
        for(int i=1;i<=la;i++)
        printf("%c",a[i]);
        for(int i=j+1;i<=la;i++)
        printf("%c",b[i]);
        printf("\n");
        }
		return 0;
    }
上一篇:KMP


下一篇:KMP算法与Manacher算法