嫦娥奔月(KMP,找循环节)及其扩展KMP

问题描述

《归妹》卦辞为:昔者恒我(姮娥)窃毋死之药于西王母,服之以(奔)月。将往,而枚占于有黄。有黄占之曰:“吉。翩翩归妹,独将西行。逢天晦芒,毋惊毋恐,后且大昌”。恒我遂托身于月,是为蟾蠩。

嫦娥去了广寒宫以后每天特别无聊,只有小兔子陪她玩。有一天,天蓬元帅来找她去东海玩,虽然她很想出去玩,可是她是后羿的妻子啊,心里又喜欢吴刚,更何况玉帝还在那盯着呢,怎么可以随随便便和他出去呢?于是她出了一道题给天蓬,答应只要他做出来了就随他一起去东海。

题目如下:

1.对于一个字符串S,定义以下n个字符串,S[i]表示一个这样的字符串:截取S的前i个字符,让截取部分的第一个字符接在剩下部分的最后一个字符后面。例:对于字符串S =“abcdef”,S[2]表示字符串“cdefab”。

2.将n个字符串进行分组,相同字符串为一组。

3.定义序列L,表示为每一组的字符串的编号按从小到大排列的序列。

4.按照L的字典序从小到大输出所有分组。

例:S =“abab”,S[0] =“abab”,S[1] =“baba”,S[2] =“abab”,S[3] =“baba”。分组L[]为(0, 2),(1, 3)。对L数组排序后:L[1]=(0, 2),L[2]=(1, 3)。因为0比1小。

这题太难了,为了成功的约到嫦娥,天蓬找到了你来帮忙,你能帮帮他吗?

输入描述

输入包含一个字符串S。

1 ≤ |S| ≤ 1000000。

S只包含小写字母。

输出描述

第一行输出一个整数K,表示分组个数。

接着K行,每行第一个整数n表示该分组有多少个字符串,后面接着n个整数,表示该分组的字符串的编号。

样例输入

abab


deadbeef

样例输出

2
2 0 2
2 1 3


8
1 0
1 1
1 2
1 3
1 4
1 5
1 6
1 7

这道题,我一开始直接模拟去求解,用了一个map,一个multimap和一个set来求解。遍历一遍,用set将每一次截取后重新拼接的字符串存在set容器中,同时用map计数每一个字符串出现的次数。再用multimap将每一次的截取位置保存下来。然后遍历一遍输出。(但却爆内存了 )

附错误代码(爆内存)

#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<set>
#include<map>
#include<queue>
#include<vector>
#include<algorithm>

using namespace std;

const int maxn = 1e6+5;
int main()
{
	string s;
	map<string, int>m;
	multimap<string, int>mm;
	set<string>S;
	while(cin >> s){
		for(int i = 0; i < s.length(); i++){
			string temp = s.substr(i, s.length()-i) + s.substr(0, i);
			S.insert(temp);
			m[temp]++;
			mm.insert(make_pair(temp, i));
		}
	
		map<string, int>::iterator it1;
		multimap<string, int>::iterator it2;
		cout << S.size() << endl;
		for(it1 = m.begin(); it1 != m.end(); it1++){
			cout <<(*it1).second << " ";
			for(it2 = mm.begin(); it2 != mm.end(); it2++){
				if((*it1).first == (*it2).first) cout << (*it2).second << " ";
			}
			cout << endl;
		}
		
		m.clear(); mm.clear(); S.clear();		
	}	
	
	return 0;	
} 

 

后来看了题解才知道,原来这道题可以用KMP,找循环节来做。

首先我们可以用求解一个长度为n的字符串的next数组,从而找到字符串的循环节cir,那么该字符串就有cir组,每组有n/cir个字符。每次输出字符编号从0开始。

 

关于KMP的相关知识:KMP详解

 

ac代码

#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>

using namespace std;

const int maxn = 1e6+5;
int nxt[maxn];
char str[maxn];

void getNext(char str[]){
	int t = strlen(str);
	nxt[0] = -1;
	int j = 0;
	int k = -1;
	while(j < t){
		if(k == -1||str[j] == str[k]) nxt[++j] = ++k;
		else k = nxt[k];
	}
}

int main()
{
	while(scanf("%s", str) != EOF){
		getNext(str);
	
		int t = strlen(str);
		int cir = t - nxt[t];      //循环节
		if(t%cir) cir = t;
	
		printf("%d\n", cir);
		int num = t/cir;         //有多少组
		
		for(int j = 0; j < cir; j++){
			printf("%d",num);
			for(int k = j; k < t; k += cir){
				printf(" %d", k);
			} 
			printf("\n");
		}
		
	}
		
	return 0;
}

 

扩展KMP(求原串S1的每一个后缀子串与模式串S2的最长公共前缀长度)es[]数组去存

const int maxn=100010;   //字符串长度最大值
int next[maxn],ex[maxn]; //ex数组即为extend数组
//预处理计算next数组
void GETNEXT(char *str)
{
    int i=0,j,po,len=strlen(str);
    next[0]=len;//初始化next[0]
    while(str[i]==str[i+1]&&i+1<len)//计算next[1]
    i++;
    next[1]=i;
    po=1;//初始化po的位置
    for(i=2;i<len;i++)
    {
        if(next[i-po]+i<next[po]+po)//第一种情况,可以直接得到next[i]的值
        next[i]=next[i-po];
        else//第二种情况,要继续匹配才能得到next[i]的值
        {
            j=next[po]+po-i;
            if(j<0)j=0;//如果i>po+next[po],则要从头开始匹配
            while(i+j<len&&str[j]==str[j+i])//计算next[i]
            j++;
            next[i]=j;
            po=i;//更新po的位置
        }
    }
}
//计算extend数组
void EXKMP(char *s1,char *s2)
{
    int i=0,j,po,len=strlen(s1),l2=strlen(s2);
    GETNEXT(s2);//计算子串的next数组
    while(s1[i]==s2[i]&&i<l2&&i<len)//计算ex[0]
    i++;
    ex[0]=i;
    po=0;//初始化po的位置
    for(i=1;i<len;i++)
    {
        if(next[i-po]+i<ex[po]+po)//第一种情况,直接可以得到ex[i]的值
        ex[i]=next[i-po];
        else//第二种情况,要继续匹配才能得到ex[i]的值
        {
            j=ex[po]+po-i;
            if(j<0)j=0;//如果i>ex[po]+po则要从头开始匹配
            while(i+j<len&&j<l2&&s1[j+i]==s2[j])//计算ex[i]
            j++;
            ex[i]=j;
            po=i;//更新po的位置
        }
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值