HDU 6096:妙用AC自动机(666)

本文介绍了一种使用AC自动机解决特定字符串匹配问题的方法,即在一个大型字典中寻找同时具有指定前缀和后缀的单词。通过将查询字符串进行特殊组合并构建AC自动机,再利用字典中的单词进行匹配搜索,从而高效地解决问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:给出一个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; 
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值