题目大意
现在有N个单词,Q次询问,每次询问包含两个字符串a和b,求出以a为前缀且以b为后缀的单词共有多少个,其中a和b不互相覆盖。
解题思路
题目中所说的a和b不互相覆盖的含义是当 a =“ac”b = “cm”时,“acm”串不满足条件,因为其以a为前缀且以b为后缀时,a和b相互覆盖了。
题解中所说的做法没太看懂,现在给出一种巧妙构造并利用AC自动机解题的方法。
由于询问的时候,每次给出两个字符串并不便于操作,不妨将后缀与前缀拼接在一起,并使用特殊的符号进行标记。将所有询问离线,建立AC自动机。并将给出的N个单词拼在一起,具体操作为:
1.将每个单词复制一份拼接在原单词后面,中间使用一种分隔符连接,此分隔符与询问中的区分前后缀的分隔符相同,构成新的单词。
2.将新的单词依次连接起来,中间使用另一种分隔符连接,将所有单词连接成一篇文章。
3.原问题转化为,求出一篇文章中的存在的每种模式串的数量,是经典的AC自动机求解问题。
为了方便理解,此处以样例作为栗子
Sample Input
**1
4 4
aba
cde
acdefa
cdef
a a
cd ef
ac a
ce f**
将给出的单词拼接成文章为:
aba|aba#cde|cde#acdefa|acdefa#cdef|cdef#
将前后缀处理为新的字符串:
S1=“a|a”,S2=”ef|cd”,S3=“a|ac”,S4 = “f|ce”
将S1-S4依次insert,建成AC自动机,离线询问
扫描文章,对于每次询问依次回答即可
小Tips:由于题目中给出了前后缀不能覆盖的条件,所以扫描文章的时候需要加入特判。
附上代码
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#define maxnode 2005000
#define sigma 30
using namespace std;
typedef long long ll;
int cnt=1;
char que[2550000],in[2000000],head[2000000],tail[2000000],key[2000000];
int ask[1001000],res[1010000];
struct ac_automation
{
int ch[maxnode][sigma];
int val[maxnode];
int last[maxnode];
int f[maxnode];
int num[maxnode];
int dep[maxnode];
int sz,ans;
void clear()
{
sz=1;ans=0;
memset(ch[0],0,sizeof(ch[0]));
memset(last,0,sizeof(last));
memset(val,0,sizeof(val));
memset(num,0,sizeof(num));
}
int idx(char sign)
{
if(sign>='a'&&sign<='z') return sign-'a';
else if(sign=='|') return 26;
}
void insert(char s[],int k)
{
int u=0;
int mx=strlen(s);
for(int i=0;s[i];i++)
{
int c=idx(s[i]);
if(!ch[u][c])
{
memset(ch[sz],0,sizeof(ch[sz]));
ch[u][c]=sz++;
}u=ch[u][c];
}
dep[u]=mx;
if(!val[u])
val[u]=cnt++;
ask[k]=val[u];
}
void build()
{
f[0]=0;
queue<int>q;
for(int i=0;i<sigma;i++)
{
if(ch[0][i])
{
f[ch[0][i]]=0;
q.push(ch[0][i]);
last[ch[0][i]]=0;
}
}
while(!q.empty())
{
int now=q.front();
q.pop();
for(int i=0;i<sigma;i++)
{
int son=ch[now][i];
if(!son)
{
ch[now][i]=ch[f[now]][i];
continue;
}
q.push(son);
f[son]=ch[f[now]][i];
last[son]=val[f[son]]?f[son]:last[f[son]];
}
}
}
void find(char *s)
{
int u=0,left=0,right=0,mid=0;
for(int i=0;s[i];i++)
{
if(s[i]=='#') left=i;
else if(s[i]=='|') mid=i;
right=i;
int c=idx(s[i]);
u=ch[u][c];
if(val[u]&&dep[u]<=mid-left)//特殊判断操作,当前后缀之和小于等于原单词长度才进行计数
print(u);
else
print(last[u]);
}
}
void print(int u)
{
if(u)
{
res[val[u]]++;
print(last[u]);
}
}
}ac;
int main()
{
// freopen("1001.in","r",stdin);
// freopen("out.txt","w",stdout);
int t,n,q;
scanf("%d",&t);
while(t--)
{
memset(res,0,sizeof(res));
ac.clear();
scanf("%d%d",&n,&q);
int cont=1;
que[0]='#';
for(int i=0;i<n;i++)
{
scanf("%s",in);
int len=strlen(in);
for(int j=0;j<len;j++)
que[cont+j]=in[j];
que[cont+len]='|';
cont=cont+len+1;
for(int j=0;j<len;j++)
que[cont+j]=in[j];
que[cont+len]='#';
cont=cont+len+1;
}
que[cont]=0;
for(int i=1;i<=q;i++)
{
scanf("%s",head);
scanf("%s",tail);
int len=strlen(tail);
tail[len]='|';len++;
int len2=strlen(head);
for(int j=0;j<len2;j++)
tail[len+j]=head[j];
tail[len+len2]=0;
ac.insert(tail,i);
}
ac.build();
ac.find(que);
for(int i=1;i<=q;i++)
printf("%d\n",res[ask[i]]);
}
return 0;
}