问题描述
《归妹》卦辞为:昔者恒我(姮娥)窃毋死之药于西王母,服之以(奔)月。将往,而枚占于有黄。有黄占之曰:“吉。翩翩归妹,独将西行。逢天晦芒,毋惊毋恐,后且大昌”。恒我遂托身于月,是为蟾蠩。
嫦娥去了广寒宫以后每天特别无聊,只有小兔子陪她玩。有一天,天蓬元帅来找她去东海玩,虽然她很想出去玩,可是她是后羿的妻子啊,心里又喜欢吴刚,更何况玉帝还在那盯着呢,怎么可以随随便便和他出去呢?于是她出了一道题给天蓬,答应只要他做出来了就随他一起去东海。
题目如下:
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的位置 } } }