给出匹配串和模式串,求模式串在匹配串中出现的位置。
设匹配串长度为n,模式串长度为m。
显然暴力的时间复杂度是 O ( n m ) O(nm) O(nm)
但是想想可以发现,每次匹配时一旦失配所有的相同信息全部丢掉。
至于预处理前后段相同部分,其实类似上面,只不过是自己匹配自己
如果失配就不断迭代,具体不细述因为太水
时间复杂度 O ( n + m ) O(n+m) O(n+m)
# include
# include
# include
# define fo(a,b,c) for (a=b; a<=c; a++)
# define fd(a,b,c) for (a=b; a>=c; a--)
using namespace std;
char s[100000];
char t[100000];
int next[100000];
int i,j,k,l,len;
int main()
{
scanf("%s",s);
scanf("%s",t);
len=strlen(t)-1;
j=-1;
next[0]=-1;
fo(i,1,len)
{
while ((j>-1) && (t[j+1]!=t[i]))//不断迭代
j=next[j];
if (t[j+1]==t[i])//如果匹配成功就扩展
j++;
next[i]=j;
}
j=-1;
fo(i,0,strlen(s)-1)
{
while ((j>-1) && (t[j+1]!=s[i]))//不断迭代
j=next[j];
if (t[j+1]==s[i])//如果匹配成功就扩展
j++;
if (j==len)//找到就输出,并重新匹配
{
cout<<i-len<<endl;
j=next[j];
}
}
return 0;
}
JZOJ5096. 【GDOI2017 day1】房屋购置
Description
涛涛最近准备要结婚了,但这在这之前他需要买套房子。买房子的确是人生大事哟,所以涛涛要好好斟酌。
于是他去房屋中介网上爬到了各种房子的数据,并得到了这些房子的特征,但是现在有一个问题感到很困惑, 但他知道你编程贼 6,所以希望你能帮帮他。
现在有 N 幢房子,每幢房子用一个字符串 si 来描述。但同样的房子不同的开发商会用不同的词汇来描述。
某些字符串存在缩写,例如 swimmingpool 可以简写为 pool 。
现在有 M 条特征的简写规则,每条规则包含两个字符串 ai , bi , 表示将所有子串中的 ai 替换成 bi。
一个字符串可能会被同一条规则匹配多次,优先替换最左边的,且新生成的字符串会不会被重新用于该规则 的匹配。不同的规则之间按照严格的顺序关系执行 (详见样例)。
现在你需要对已有的 N 条字符串通过 M 条有顺序的替换规则进行缩写。
Input
第一行有两个正整数 N,M,代表 N 幢房子,和 M 条替换规则。
接下来 N 行,每行一个字符串 si
接下来 M 行,每行两个字符串 ai , bi,中间用空格隔开。
保证所有输入的字符串只会出现小写字母。
Output
输出 N 行每行一个字符串,代表特征替换后的字符串。
Sample Input
Sample Input1:
1 1
aaaaaaa
aaa ba
Sample Input2:
1 1
ababababc
aba a
Sample Input3:
3 3
swimmingswimmingpool
catallow
dogallow
cat pet
dog pet
swimmingpool pool
Sample Input4:
2 3
aaaabbb
bbbbaaa
aaaa cc
cbbb a
bbbb a
Sample Output
Sample Output1:
babaa
Sample Output2:
ababc
Sample Output3:
swimmingpool
petallow
petallow
Sample Output4:
ca
aaaa
Data Constraint
20% 的数据:1 ≤ |si |, |ai |, |bi | ≤ 100 (|s| 表示字符串 s 的长度)
50% 的数据:1 ≤ |si |, |ai |, |bi | ≤ 30000
100% 的数据:1 ≤ |si |, |ai |, |bi | ≤ 100000, 1 ≤ N, M ≤ 20, |ai | ≥ |bi |。
直接搞就行了。。。
由于是N久前Pascal写的,所以可(wu)能(bi)不优美
var
a:array[1..20,0..200000] of longint;
b:array[1..20,0..200000] of longint;
s1,s2:array[0..200000] of longint;
next:array[0..200000] of longint;
n,m,i,j,k,l,len,ii,last:longint;
bz,bz2:boolean;
ch:char;
begin
assign(Input,'house.in'); reset(Input);
assign(Output,'house.out'); rewrite(Output);
readln(n,m);
for i:=1 to n do
begin
read(ch);
while ch in['a'..'z'] do
begin
inc(a[i,0]);
a[i,a[i,0]]:=ord(ch);
read(ch);
end;
readln;
end;
for i:=1 to m do
begin
s1[0]:=0;
s2[0]:=0;
read(ch);
while ch<>' ' do
begin
inc(s1[0]);
s1[s1[0]]:=ord(ch);
read(ch);
end;
read(ch);
while ch in['a'..'z'] do
begin
inc(s2[0]);
s2[s2[0]]:=ord(ch);
read(ch);
end;
readln;
k:=0;
for j:=2 to s1[0] do
begin
while (k>0) and (s1[k+1]<>s1[j]) do
k:=next[k];
if s1[k+1]=s1[j] then
inc(k);
next[j]:=k;
end;
for k:=1 to n do
begin
l:=0;
len:=a[k,0];
a[k,0]:=0;
while l<=len do
begin
bz:=false;
bz2:=false;
last:=l;
j:=0;
while (j<s1[0]) and (l<=len) do
begin
inc(l);
while (j>0) and (a[k,l]<>s1[j+1]) do
j:=next[j];
if a[k,l]=s1[j+1] then
inc(j);
end;
if j<s1[0] then
break;
j:=next[j];
for ii:=last+1 to l-s1[0] do
begin
inc(a[k,0]);
a[k,a[k,0]]:=a[k,ii];
end;
for ii:=1 to s2[0] do
begin
inc(a[k,0]);
a[k,a[k,0]]:=s2[ii];
end;
end;
if last<len then
for l:=last+1 to len do
begin
inc(a[k,0]);
a[k,a[k,0]]:=a[k,l];
end;
end;
end;
for i:=1 to n do
begin
for j:=1 to a[i,0] do
write(chr(a[i,j]));
writeln;
end;
close(Input); close(Output);
end.
全称是Aho-Corasick
可以支持多模式串匹配(相比之下,KMP只能支持单模式串匹配)
思想类似在trie上建KMP
(不懂trie可以自己去找资料或脑补)
定义fail[x]=y,则满足y节点是x节点的最长后缀
比如"bac"是"aba bac"的后缀。
特殊的,如果x节点是根节点的儿子,则将fail[x]设为根节点。
fail指针类似KMP的next数组,从父节点不断往上跳,如果跳到某个节点有和当前结点一样的儿子,就把fail设为那个儿子。
所以AC自动机=trie+KMP
摘自http://blog.csdn.net/a_crazy_czy/article/details/48029883
设匹配串长度为n,模式串共m个,第i个记为si。
可以证明AC自动机时间复杂度为O(n+∑length(si))
# include
# include
# include
# define fo(a,b,c) for (a=b; a<=c; a++)
# define fd(a,b,c) for (a=b; a>=c; a--)
using namespace std;
int n,i,j,k,l,len,h,t;
char s[1000];
char tt[1000];
int T[100000][26];
char ch[100000];
bool bz[100000];
int fa[100000];
int fail[100000];
int d[100000];
void _new(int t,char c)
{
len++;
T[t][int(c)-97]=len;
fa[len]=t;
ch[len]=c;
}
void _printf(int t)
{
if (t>1)
_printf(fa[t]);
else
{
printf("\n");
printf("%d\n",i+1);
return;
}
printf("%c",ch[t]);
}
int main()
{
scanf("%d",&n);
scanf("%s",s);
len=1;
fo(i,1,n)//建trie
{
scanf("%s",tt);
k=1;
fo(j,0,strlen(tt)-1)
{
if (!T[k][int(tt[j])-97])
_new(k,tt[j]);
k=T[k][int(tt[j])-97];
}
bz[k]=1;
}
fail[1]=1;
fo(i,0,25)//初始化根节点的儿子的fail
fail[T[1][i]]=1;
h=0;
t=1;
d[1]=1;
while (h<t)//建fail指针
{
h++;
fo(i,0,25)
if (T[d[h]][i])
{
d[++t]=T[d[h]][i];
if (d[h]>1)
{
j=fail[d[h]];
while ((j>1) && (!T[j][i]))//不断迭代
j=fail[j];
if (T[j][i])
fail[d[t]]=T[j][i];
else
fail[d[t]]=1;
}
}
}
j=1;
fo(i,0,strlen(s)-1)//匹配
{
k=int(s[i])-97;
while ((j>1) && (!T[j][k]))
j=fail[j];
if (T[j][k])//如果找到就往下走
{
j=T[j][k];
if (bz[j])
_printf(j);
}
l=j;
while (l>1)//判断当前位置的后缀是否存在于模式串中(可能会重叠)
{
l=fail[l];
if (bz[l])
_printf(l);
}
}
return 0;
}
JZOJ3472. 【NOIP2013模拟联考8】匹配(match)
Description
给定k个字符串以及长度为n的母串可选字母的集合,问母串要完整出现给定的k个字符串的方案数,答案模1000000007,字符仅包含小写字母。
Input
第一行两个整数n、k,表示字符串的长度和给定字符串的个数。
接下来k行每行一个字符串。
接下来一行1个整数m表示可选字母集合内元素个数。
接下来一行给出一个长为m的字符串,表示字母的集合(可能有重复)。
Output
一个整数ans,表示方案数。
Sample Input
3 2
cr
rh
4
acrh
Sample Output
1
【样例解释】
只有crh符合。
Data Constraint
30%的数据n<=10,m<=3。
60%的数据n<=40。
另有10%的数据k=0。
另有10%的数据m=1。
100%的数据n<=100,m<=10,k<=8,给定字符串长度<=30。
状压Dp+AC自动机
设f[i][j][k]表示当前枚举到字符串第i位,在AC自动机上位置为j,匹配成功的字符串状态为k(状压)
然后建好AC自动机后搞一遍就行了。
# include
# include
# include
# define fo(a,b,c) for (a=b; a<=c; a++)
# define fd(a,b,c) for (a=b; a>=c; a--)
# define mod 1000000007
using namespace std;
int p[9]={0,1,2,4,8,16,32,64,128};
int n,m,i,j,k,l,len,h,t,L,J,K,I,ii;
bool ch[26];
char tt[1000];
int T[90][26];
int bz[90];
int CH[90];
int fa[90];
int fail[90];
int d[90];
int f[101][90][256];
long long ans;
char Ch;
void _new(int t,char c)
{
len++;
T[t][int(c)-97]=len;
fa[len]=t;
}
void Aho_Corasick()
{
scanf("%d%d",&n,&l);
L=p[l]*2-1;
len=1;
fo(i,1,l)
{
scanf("%s",tt);
k=1;
fo(j,0,strlen(tt)-1)
{
if (!T[k][int(tt[j])-97])
_new(k,tt[j]);
k=T[k][int(tt[j])-97];
}
bz[k]=i;
}
fail[1]=1;
fo(i,0,25)
fail[T[1][i]]=1;
h=0;
t=1;
d[1]=1;
while (h<t)
{
h++;
fo(i,0,25)
if (T[d[h]][i])
{
d[++t]=T[d[h]][i];
if (d[h]>1)
{
j=fail[d[h]];
while ((j>1) && (!T[j][i]))
j=fail[j];
if (T[j][i])
fail[d[t]]=T[j][i];
else
fail[d[t]]=1;
}
}
}
}
int main()
{
Aho_Corasick();
scanf("%d\n",&m);
fo(i,1,m)
{
scanf("%c",&Ch);
CH[i]=int(Ch)-97;
}
if (len==1)
{
ans=1;
fo(i,1,n)
ans=(ans*m)%mod;
printf("%d\n",ans);
return 0;
}
f[0][1][0]=1;
fo(i,0,n-1)
{
fo(j,1,len)
{
fo(k,0,L)
if (f[i][j][k])
{
fo(ii,1,m)
{
l=CH[ii];
J=j;
K=k;
while ((J>1) && (!T[J][l]))
J=fail[J];
if (T[J][l])
J=T[J][l];
I=J;
while (I>1)
{
if (bz[I])
K|=p[bz[I]];
I=fail[I];
}
f[i+1][J][K]=(f[i+1][J][K]+f[i][j][k])%mod;
}
}
}
}
ans=0;
fo(i,1,len)
ans=(ans+f[n][i][L])%mod;
printf("%d\n",ans);
return 0;
}
用来处理回文子串的问题。
1、求回文子串的种类。
2、求每种回文子串出现次数。
3、求匹配串的前缀中的回文子串。
4、求以下标i为结尾的回文子串个数。
思想跟AC自动机类似,每个节点都代表一个回文串
则每个节点都可以向两边同时加一个字符来变成新的回文串。
定义fail[x]=y表示x的最长后缀位置是y
因为回文串分奇偶性,所以定义两个根
偶数根长度为0,奇数根长度为**-1**(没错就是-1,因为可以通过扩展得到长度为1的串)
然后偶数根的fail设为奇数根。
每次从当前节点(初始设为偶数根)扩展时,沿着fail指针一直跳,直到发现某个串可以扩展就扩展。
如果扩展了节点,那么新节点的cnt(计数)设为1,否则+1
每次扩展长度+2
如果扩展了一个新节点,怎样求它的fail指针?
根据回文的性质,蓝色部分一定是一个回文串
所以只需要沿着fail指针继续向上跳来找一个能扩展的点
能扩展的点不仅是要有相应的儿子,还要能在当前情况下扩展
(就是上面这点坑了我一个小时)
(如果没有找到就把fail设为偶数根)
还有一点,因为每个长串包含了短串,所以最后要从后往前沿着fail来累加cnt。
其实理解了AC自动机后学这个并不难
时间复杂度 O ( ∣ S ∣ ) O(|S|) O(∣S∣)即 O ( n ) O(n) O(n)
然而我并不会证
JZOJ3654. 【APIO2014】回文串
也就是本算法的出处
(补充一下,回文树是由战斗民族的大佬于2014年发明的)
Description
考虑一个只包含小写拉丁字母的符串 s。我们定义 s的一个子串 t的“出现值”为 t在 s中的出现次数乘以t的长度。 请你求出s的所有 回文子串中的最大出现值。
Input
输入只有一行,为一个只包含小写字母 (a−z) 的非空字符串 s。
Output
输出 一个整数,为 所有 回文子串 的最大 出现 值。
Sample Input
输入1:
abacaba
输入2:
www
Sample Output
输出1:
7
输出2:
4
裸题瞎搞。
# include
# include
# include
# define fo(a,b,c) for (a=b; a<=c; a++)
# define fd(a,b,c) for (a=b; a>=c; a--)
# define max(x,y) (x>y?x:y)
using namespace std;
int tr[300010][26];
int fa[300010];
long long len[300010];
long long cnt[300010];
char s[300010];
int i,j,k,l,n,last,L;
long long ans;
char ch[300010];
int f[300010];
void New(int t,int x)
{
n++;
tr[t][x]=n;
len[n]=len[t]+2;
ch[n]=char(x+97);
f[n]=t;
}
int main()
{
freopen("palindrome.in","r",stdin);
freopen("palindrome.out","w",stdout);
scanf("%s",&s);
L=strlen(s);
fd(i,L,1)
s[i]=s[i-1];
s[0]=' ';
n=1;
fa[0]=1;
fa[1]=1;
len[0]=0;
len[1]=-1;
last=0;
fo(i,1,L)
{
k=int(s[i])-97;
while (s[i]!=s[i-len[last]-1])//不断迭代查找
last=fa[last];
j=fa[last];
while ((len[j]>-1) && (s[i]!=s[i-len[j]-1]))//继续向上扩展
j=fa[j];
j=tr[j][k];
if (!tr[last][k])//新建节点
{
New(last,k);
fa[tr[last][k]]=j;
}
last=tr[last][k];//从当前点转移到子树
cnt[last]++;
}
fd(i,n,2)
{
cnt[fa[i]]+=cnt[i];//累加答案
ans=max(len[i]*cnt[i],ans);
}
printf("%lld\n",ans);
fclose(stdin);
fclose(stdout);
return 0;
}
Palindromic Tree——回文树【处理一类回文串问题的强力工具】
论如何优雅的处理回文串 - 回文自动机详解.
回文树介绍(Palindromic Tree)