都是比较简单SB的东西,求各位去WC的神犇勿喷。
1、Trie
(1)[BZOJ 1212][HNOI 2004]L语言
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;
}
2、AC自动机
(1)[BZOJ 3172][Tjoi 2013]单词
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;
}
3、KMP
(1)[POJ 3461]Oulipo
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;
}
4、后缀数组
(1)[BZOJ 3224]Tyvj 1728 普通平衡树
http://www.lydsy.com/JudgeOnline/problem.php?id=3224
基础的平衡树操作。对序列中单点修改、求第k小数、求某个数的排名、查询某个数的前驱后继。