【题目描述】
F刚刚和邻居发生了一场可怕的争吵,他咽不下这口气,决定佚名发给他的邻居一封脏话连篇的信。他有无限张完全相同的已经打印好的信件,都包含 N个字母(1<=N。他想剪出其中一些并且粘帖成一个很长的字母串。
F太懒了,他想用最少的次数裁剪信件。他有一把举世无双的剪刀,他可以从一封信中只剪一刀剪出连续一段。同样,剪一刀可以得到整个完整的字符串。
他想知道他最少需要剪多少刀从而获得这封M个字母的长信?
保证这总是可能的。
考虑下面38个字母的信:
THEQUICKBROWNFOXDO
GJUMPSOVERTHELAZYDOG
以及FJ想要获得的9个字母的信:
FOXDOG DOG
以上是为了读入方便,实际上这两封信就是:
THEQUICKBROWNFOXDOGJUMPSOVERTHELAZYDOG
FOXDOGDOG
由于FOXDOG已经存在了,F可以剪一刀得到FOXDOG。还剩下一个DOG,F可以选择其中任何一个DOG剪下来。
因此,他一共要剪2刀。
【输入格式】
第1行: 两个空格分隔的整数: N
第2..?行: 一些行包含N个字母,F原来拥有的信,每行不会超过80个字母。
第?...?行: 一些行包含M个字母,F想要写的信,每行不会超过80个字母。
【输出格式】
第1行: F获得他想要写的信所需要切的最少次数。
【样例输入】
38 9
THEQUICKBROWNFOXDO
GJUMPSOVERTHELAZYDOG
FOXDOG
DOG
【样例输出】
2
【来源】
J.Kuipers, 2002
注意SAM要开两倍空间,不然死得惨……
这里每次贪心地匹配最长的一段,失配后直接回rt即可,记得最后答案要加1,因为计数是失配一次才加一次。
1 #include <iostream> 2 #include <cstring> 3 #include <cstdio> 4 using namespace std; 5 const int maxn=100010; 6 int read(){ 7 char c=getchar(); 8 while(c<'A'||c>'Z') 9 c=getchar(); 10 return c-'A'; 11 } 12 int fa[maxn],len[maxn]; 13 int lst,cnt,ch[maxn][26]; 14 struct SAM{ 15 SAM(){cnt=lst=1;} 16 void Insert(int c){ 17 int p=lst,np=lst=++cnt;len[np]=len[p]+1; 18 while(p&&!ch[p][c])ch[p][c]=np,p=fa[p]; 19 if(!p)fa[np]=1; 20 else{ 21 int q=ch[p][c],nq; 22 if(len[q]==len[p]+1) 23 fa[np]=q; 24 else{ 25 nq=++cnt;len[nq]=len[p]+1; 26 memcpy(ch[nq],ch[q],sizeof(ch[q])); 27 fa[nq]=fa[q];fa[q]=fa[np]=nq; 28 while(ch[p][c]==q)ch[p][c]=nq,p=fa[p]; 29 } 30 } 31 } 32 }sam; 33 int n,m; 34 int main(){ 35 freopen("thre_letter.in","r",stdin); 36 freopen("thre_letter.out","w",stdout); 37 scanf("%d%d",&n,&m); 38 for(int i=1;i<=n;i++) 39 sam.Insert(read()); 40 int p=1,ans=0; 41 for(int i=1;i<=m;i++){ 42 int c=read(); 43 if(!ch[p][c]) 44 {ans+=1;p=1;} 45 p=ch[p][c]; 46 } 47 printf("%d ",ans+1); 48 return 0; 49 }