http://www.lydsy.com/JudgeOnline/problem.php?id=1212
不妨设f[i]=true表明当前的文章的前i个字符是合法前缀,那么很容易想到f[i]=true可以推出f[j]=true,其中i+1~j部分是一个单词。
很容易想到把每个单词都放进Trie里面,从0~文章长度遍历f[i],若f[i]=true,把文章从i+1开始的部分放入Trie中匹配,并得到新的f[j]=true的部分,最终我们遍历f数组便可找出最长合法前缀。
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <algorithm> #define MAXN 1<<20|100 #define MAXM 23 #define NUM(x) ((x)-'a') //字符x对应0~25中的编号 using namespace std; struct Trie { struct Node { int son[26],fa; //儿子指针、父结点指针 bool isBadNode; //危险结点(单词结尾)标记 }node[MAXN]; int nCount; void Insert(char *s) //插入字符串s { int p=0,len=strlen(s); //p是当前遍历到的Trie的结点,len是字符串s的长度 for(int i=0;i<len;i++) { if(!node[p].son[NUM(s[i])]) node[p].son[NUM(s[i])]=++nCount; p=node[p].son[NUM(s[i])]; } node[p].isBadNode=true; } }trie; int n,m; char str[MAXN],word[MAXM]; //待匹配的文章、要插入的单词 bool f[MAXN]; void ask(char *s,int i) //当前的字符串s是从原来的字符串的下标i处开始的 { int p=0; //遍历到的Trie树的结点编号 int t=0; //已经遍历到的字符串长度 while(*s!='\0') { if(!trie.node[p].son[NUM(*s)]) return; //无法完全匹配 p=trie.node[p].son[NUM(*s)]; s++; //字符串的指针向后移动一格 t++; //标记成功匹配上的字符串长度+1 if(trie.node[p].isBadNode) f[i+t]=true; //成功匹配上了一个单词 } if(trie.node[p].isBadNode) f[i+t]=true; } int work() //求当前文章str最长合法前缀 { int maxans; //最长合法前缀 memset(f,false,sizeof(f)); f[0]=true; int len=strlen(str+1); for(int i=0;i<=len;i++) { if(!f[i]) continue; maxans=i; ask(str+i+1,i); } return maxans; } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) { scanf("%s",word); trie.Insert(word); } for(int i=1;i<=m;i++) { scanf("%s",str+1); printf("%d\n",work()); } return 0; }
http://www.lydsy.com/JudgeOnline/problem.php?id=3172
首先将所有单词都插入AC自动机的Trie树中,记录下每个单词的危险节点(最后一个字符对应的节点)在Trie树中的编号。可以把Trie树中每个结点的fail指针看成一条边,fail指针指向的节点看成新树中这个节点的父亲,这就构成了fail树。我们记录下每个结点i在插入单词过程中被访问的次数f[i],那么在fail树中,对于每个点u及其儿子v而言,f[u]=f[u]+∑f[vi],vi是u的儿子(u代表文章的一个前缀,其fail指针所指向的点代表这个前缀的一个后缀,显然这个后缀的出现次数要算上它所在的所有前缀的出现次数)。那么单词i的出现次数就是i的危险节点的f值。
#include <iostream> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <algorithm> #define MAXN 1000010 #define MAXM 1000010 #define NUM(x) ((x)-'a') using namespace std; int pointer[210]; //pointer[i]=编号为i的模式串的危险节点的编号 struct AC_Automation { struct Trie //带fail指针的Trie树 { struct Node //Trie树结点 { int son[26]; //儿子指针 int fail; //失败指针 int cnt; //该结点在插入单词时的被匹配次数 }node[MAXN]; int nCount; void Insert(char *s,int num) //将编号为num的模式串插入到Trie中 { int p=0; while(*s!='\0') { if(!node[p].son[NUM(*s)]) node[p].son[NUM(*s)]=++nCount; p=node[p].son[NUM(*s)]; s++; node[p].cnt++; } pointer[num]=p; } }trie; //AC自动机用的Trie树 void build(int cnt) //AC自动机的初始插入cnt个模式串操作、以及构建fail指针 { char s[MAXM]; for(int i=1;i<=cnt;i++) { scanf("%s",s); trie.Insert(s,i); //在Trie树中插入第i个模式串 } //广搜建立失配指针 int q[MAXM],h=0,t=0; //广搜用的队列 for(int i=0;i<26;i++) if(trie.node[0].son[i]) { trie.node[trie.node[0].son[i]].fail=0; q[t++]=trie.node[0].son[i]; } while(h<t) { int now=q[h++]; for(int i=0;i<26;i++) { if(trie.node[now].son[i]) { int tmp=trie.node[now].fail; while(tmp!=0&&trie.node[tmp].son[i]==0) tmp=trie.node[tmp].fail; if(trie.node[tmp].son[i]) tmp=trie.node[tmp].son[i]; trie.node[trie.node[now].son[i]].fail=tmp; q[t++]=trie.node[now].son[i]; } } } while(t) { t--; trie.node[trie.node[q[t]].fail].cnt+=trie.node[q[t]].cnt; } } }acAutomation; int main() { int n; scanf("%d",&n); acAutomation.build(n); for(int i=1;i<=n;i++) printf("%d\n",acAutomation.trie.node[pointer[i]].cnt); return 0; }
http://poj.org/problem?id=3461
题目大意:给出模式串W和主串T,求W在T中的出现次数。
最基础的KMP。求出next后在主串中匹配即可,注意匹配成功后,模式串中的指针k要从最末尾处退到next[k]处,准备下一次匹配。
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <algorithm> #define MAXN 1000100 #define MAXM 10010 using namespace std; char word[MAXM],text[MAXN]; //子串与待匹配文章 int next[MAXM]; void getNext(char str[],int len) //求长度为len的模式串str的next指针 { int k=0; next[1]=0; for(int i=2;i<=len;i++) { while(k>0&&str[k+1]!=str[i]) k=next[k]; if(str[k+1]==str[i]) k++; next[i]=k; } } int match(char W[],int lenW,char T[],int lenT) //将模式串W匹配主串T,求W在T中的出现次数 { int ans=0; getNext(W,lenW); int k=0; //k=W已经匹配了的部分的长度 for(int i=1;i<=lenT;i++) { while(k>0&&W[k+1]!=T[i]) k=next[k]; if(W[k+1]==T[i]) k++; if(k==lenW) { ans++; k=next[k]; } } return ans; } int main() { int TestCase; scanf("%d",&TestCase); while(TestCase--) { memset(next,0,sizeof(next)); scanf("%s+1",word+1); scanf("%s+1",text+1); int lenw=strlen(word+1); int lent=strlen(text+1); printf("%d\n",match(word,lenw,text,lent)); } return 0; }
http://www.lydsy.com/JudgeOnline/problem.php?id=3224
基础的平衡树操作。对序列中单点修改、求第k小数、求某个数的排名、查询某个数的前驱后继。