题意:给出一个n(<=qe5)个词的字典,保证字典中没有重复的单词,单词长度<=1e5,总长度小于5e5。现在给出q(<=qe5)次询问,每次询问给出两个串 s 和 t 。要求统计字典中 前缀为 s 且后缀为 t (不重叠)的单词数量。
题解:官方题解的意思是:弄两个Trie树,然后正串的Trie树得到他下边所有终止节点。然后反串的Trie树上就每一个点都开线段树统计答案。而且必须是动态开点线段树,要不然内存炸的很恐怖。
某位大佬讲给我的题解(666!666!666!):
首先必须离线处理。先看询问:给出 s 和 t 那么我们把他组合成 t#s ,也就是表示成 (后缀)(特殊字符)(前缀)的形式,把他们加入到AC自动机中。然后再看字典中的字 p。我们把 p 变成 p#p,也就是说 任意一个(p的后缀)(#)(p的前缀)这样的串必然是 p#p的字串,而我们刚刚的询问已经在AC自动机上了,我们就拿着字典里的字p#p到AC自动机上跑一边。。。就做完了。。而且因为中间插入了特殊字符,保证了匹配成功的位置必然包括这个#,匹配成功是指匹配到了某个询问串 t#s 结束的节点。666!666!666!666!
注意:
1、由于题目中的询问会有重复,因此每个点node必须开一个vector来保存所有的Qid。也就是Qid次询问在AC自动机上在node位置结束,然后匹配到node成功的时候,要把所有的Qid的答案都++,那么可以在这个点上记录一个weight,最后在遍历统计答案就可以。
2、长度限制:考虑字典单词abcd 以及询问 abc cd。刚刚那么做会得到一个成功的匹配。但是前缀后缀出现重叠不合法。所以对于字abcd只能在那些length小于等于4的节点上累计答案。
3、本题多组测试,内存如果不回收会炸锅。而struct里面有vector是不能够free的,因为vector不是malloc得到的。因此必须开vector*,也就是vector的指针,指针本质上就是一个int数据(表示地址),这样就可以free了。
4、离线操作必须保存所有数据,用前向星可以最大限度压缩空间。
5、注意我们把所有数据的长度都double了,要开两倍以上的空间,最好更多一点。
Code:
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 200005;
const int MAX = 2000005;
char s[MAXN],t[MAXN];
char ss[MAX];
int pos[MAXN];
int ans[MAXN];
int lenn[MAXN];
int n,q;
struct AC{
AC* nxt[27];
AC* fail;
vector<int>* Qid;
int length;
int weight;
};
AC* create(){
AC* node = (AC*)(malloc(sizeof(AC)));
node->fail = NULL;
node->Qid = new vector<int>;
node->length=0;
node->weight=0;
memset(node->nxt,0,sizeof(node->nxt));
return node;
}
void insert(AC* root,const char* word,int Qid_,int Length){
AC* node = root;
const char* p = word;
while (*p){
int id = *p-'a';
if (node->nxt[id]==NULL){
node->nxt[id] = create();
}
node = node->nxt[id];
p++;
}
node->Qid->push_back(Qid_);
node->length = Length;
}
AC* que[MAX*10];
void build(AC* root){
int l=0;
int r=1;
root->fail = root;
que[1] = root;
while(l<r){
l++;
AC* q = que[l];
for (int i=0;i<=26;i++){
if (q->nxt[i]!=NULL){
if (q==root){
q->nxt[i]->fail =root;
}else{
q->nxt[i]->fail = q->fail;
while (q->nxt[i]->fail!=root&&q->nxt[i]->fail->nxt[i]==NULL){
q->nxt[i]->fail = q->nxt[i]->fail->fail;
}
if (q->nxt[i]->fail->nxt[i]!=NULL){
q->nxt[i]->fail = q->nxt[i]->fail->nxt[i];
}
}
r++;
que[r] = q->nxt[i];
}
}
}
}
void search(AC* root,char* word,int limit){
AC* node = root;
char* p = word;
while (*p){
int id = *p-'a';
while (node!=root&&node->nxt[id]==NULL){
node = node->fail;
}
if (node->nxt[id]!=NULL){
node = node->nxt[id];
}
AC* ttt = node;
while (ttt!=root){
if (ttt->length<=limit){
ttt->weight++;
}
ttt = ttt->fail;
}
p++;
}
}
void clear(AC* node){
for (int i=0;i<=26;i++){
if (node->nxt[i]!=NULL){
clear(node->nxt[i]);
}
}
free(node);
}
AC* ac;
void init(){
if (ac!=NULL){
clear(ac);
}
ac = create();
memset(ans,0,sizeof(ans));
}
string temp,temp2;
void input(){
scanf("%d%d",&n,&q);
int ssptr = 0;
for (int i=1;i<=n;i++){
pos[i]=ssptr;
scanf("%s",ss+ssptr);
int len = strlen(ss+ssptr);
lenn[i] = len;
ssptr+=len;
ss[ssptr] = 'z'+1;
for (int j=1;j<=len;j++){
ssptr++;
ss[ssptr] = ss[ssptr-len-1];
}
ss[++ssptr] = '\0';
ssptr++;
}
for (int i=1;i<=q;i++){
scanf("%s %s",s,t);
temp = t;
temp2 = s;
temp = temp+(char)('z'+1)+s;
insert(ac,temp.c_str(),i,temp.length()-1);
}
}
void accum(AC* root){
AC* node = root;
for (int i=0;i<=26;i++){
if (node->nxt[i]!=NULL){
accum(node->nxt[i]);
}
}
for(int td:*node->Qid){
ans[td]+=node->weight;
}
}
void solve(){
build(ac);
for (int i=1;i<=n;i++){
search(ac,ss+pos[i],lenn[i]);
}
accum(ac);
for (int i=1;i<=q;i++){
printf("%d\n",ans[i]);
}
}
void print(){
for (int i=1;i<=n;i++){
printf("%s length =%d\n",ss+pos[i],lenn[i]);
}
}
int main(){
// freopen("1001.in","r",stdin);
//freopen("1001.out","w",stdout);
int t;
scanf("%d",&t);
while (t--){
init();
input();
solve();
}
return 0;
}