题目描述
对于任意给定的字符串s,已知t为s的子串,寻找到t中每一位字母依照原来顺序排列时,在母串s中的坐标集(但不必连续)。期望得到所有可能的坐标集中字典序最小的一则。
输入
第一行输入字符串s,长度小于200,000。
第二行输入字符串t,长度小于10,000。
输出
字典序最小的一行整数,代表t中每一个字符在s中的位置。
示例
测试输入
iidsfnxksksndaafdsignksdmweosaf
sign
期望输出
1 20 21 23
注意这里按照sign排序则得到4 19 20 21,但由于按照igns排序得到1 20 21 23,按照字典序最小的要求,显然由于1 < 4 故而最终答案是1 20 21 23。
解题
显然对于二十万和一万的字符串长度,通过序列自动机可以更方便(节省时间)得到结果。
先构造序列自动机的函数:
int now[30]; //记录字母最早出现的位置
int nex[MAXN][30]; //nex[i][j]记录在主串第i个字符之后('a'+j)第一次出现的位置
void init(){
memset(now,-1,sizeof(now));
int lens=strlen(s);
for(int i = lens - 1;i >= 0;i--){
for(int j = 0;j < 26;j++)
nex[i][j] = now[j];
now[s[i] - 'a'] = i;
}
}
注意需要建立外部的大循环来遍历每一次t。
可以如下在第一次进行读取时就判断字典序,这样可以在前面不满足条件的时候直接continue下一轮,避免在无意义段落上浪费时间。
#include <iostream>
#include <algorithm>
#include <cstring>
#define MAXN 200000
#define LONG 10000
using namespace std;
char s[MAXN], t[LONG], target[LONG];
int now[30];//记录字母最早出现的位置
int nex[MAXN][30];//nex[i][j]记录在主串第i个字符之后('a'+j)第一次出现的位置
int ans[MAXN], cmpr[MAXN];
int temp = LONG;
int flag_dblout = 0, flag_cmpr = 0;
void init(){
memset(now,-1,sizeof(now));
int lens=strlen(s);
for(int i=lens-1;i>=0;i--){
for(int j=0;j<26;j++)
nex[i][j]=now[j];
now[s[i]-'a']=i;
}
}
int main(){
memset(cmpr, 0, sizeof(cmpr));
memset(ans, MAXN, sizeof(ans));
int loc,len;
scanf("%s",s);
getchar();
scanf("%s",target);
len = strlen(target);
init();
int n = 0;
while(n < len){ //主轮回
flag_dblout = 0;
flag_cmpr = 0;
n++;
for(int g=0;g<len-n;g++){ //换序部分
t[g]=target[g+n];
} for(int g=len-n;g<len;g++){
t[g]=target[g+n-len];
}
//cout << "###########下面开始第"<< n <<"次轮回###########" << endl << "轮回项目是:"<< t << endl; //【检验代码】
loc = now[t[0]-'a']; //特判
if (loc <= ans[0]) {
cmpr[0] = loc;
if(loc == ans[0])flag_cmpr = 1; //首位如果相等就做记号留到后面再比较
} else continue; //首位并没有比先前成功案例小或一样就直接走下一轮
//cout << "************" << ans[0] +1 << endl; //【检验代码】打印第一个字符首次出现位置
//cout << cmpr[0] +1 << endl; //【检验代码】打印第一个字符首次出现位置
//具体实现寻找后续字母位置
for(int i=1;i<len;i++){
loc=nex[loc][t[i]-'a'];
if (flag_cmpr == 0) cmpr[i] = loc; //前一位如果已经小了就直接替换
else {
if(loc <= ans[i]) { //这一位如果依然相等就做记号留到后面再比较
cmpr[i] = loc;
if(loc == ans[i])flag_cmpr = 1;
}
else {//首位并没有比先前成功案例小就做记号来直接走下一轮
flag_dblout = -1;
break;
}
}
//cout << "**********" << ans[i]+1 << endl; //【检验代码】打印下一个字符在前一个字符出现后首次出现位置
//cout << cmpr[i]+1 << endl; //【检验代码】打印下一个字符在前一个字符出现后首次出现位置
if(loc==-1){ //找不到就直接跳出循环
//cout << "!!!!!!!!!!!!!!!!No!!!!!!!!!!!!!!!!" << endl; //【检验代码】
break;
}
} if(loc==-1||flag_dblout==-1)continue; //找不到跳出循环后直接走下一轮
for(int i=0;i<len;i++){
ans[i] = cmpr[i];
}
}
printf("%d",ans[0]+1);
for(int i = 1;i<len;i++){
printf(" %d",ans[i]+1);
} printf("\n");
return 0;
}