CodeForces - 1252D Find String in a Grid(AC自动机)

题目链接:点击查看

题目大意:给出一个 n * m 的字符矩阵,再给出 q 次询问,每次询问需要回答给出字符串在字符矩阵中出现了多少次,规定在字符矩阵中查找某个字符串,只能先向右 a 个单位,再向下 b 个单位所得到的路径上的字符串,a 和 b 可以等于 0 ,也就是至多存在一次拐角,如样例的 ABC:

 题目分析:官方题解的做法是后缀数组+树状数组,但过于复杂,看不懂,民间题解是AC自动机,这里讲一下AC自动机怎么做吧

首先 n 和 m 是 500 的,我们如果枚举每个起始位置是 n * n 的复杂度,然后再枚举拐点又是 O( n ) 的复杂度,最后再枚举终点位置也是一层 O( n ) ,换句话说,一个 n * m 的矩阵中一共有 n^4 个字符串,因为数量过于庞大,肯定是不能直接枚举的,但是仔细观察一下不难发现,n^4 个字符串,有很多字符串都是隶属于前面字符串的子串,基于此,我们只需要确定一下有多少个不同的原串即可,如果只是枚举原串的话,可以 n * n 枚举拐点,然后 O( n ) 以拐点为中间点,向左向下一直扩展到最大即可,这样原串的数量是 n^3 的

到此为止我们就可以用 AC 自动机暴力匹配了,先将所有的匹配串扔到 AC 自动机中,然后 n^3 枚举拐点去查询原串,将所有的前缀都进行计数,最后在 fail 树上求一下 sum 和就是答案了,注意去重

最后讲一下为什么上面的策略是正确的,因为 n^3 将原串的所有前缀都计数了,而每个节点的 fail 指向的节点是当前前缀的最长后缀,类似于后缀数组的思想,在 AC 自动机上,可以用 “前缀的后缀” 来表示所有的子串,这样一来所有的子串都可以得到计数,从而达到题目中的要求

然后就是去重问题了,简单画个图:

如图所示,如果红色圈起来的字符串是匹配成功的一个匹配串,那么其在第一次拐点下面,第二次拐点下面和第三次拐点下面都会被计数一次,也就是一共会被计数三次,但实际上重复计数了两次,这样的话我们只需要在第一行的时候才给予计数,其余的时候都减去重复的贡献即可

代码:
 

#include<iostream>
#include<cstdio>
#include<string>
#include<ctime>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<stack>
#include<climits>
#include<queue>
#include<map>
#include<set>
#include<sstream>
#include<cassert>
#include<bitset>
#include<unordered_map>
using namespace std;
 
typedef long long LL;
 
typedef unsigned long long ull;
 
const int inf=0x3f3f3f3f;
 
const int N=2e5+100;
 
char maze[510][510],s[N];
 
int fail[N],id[N],trie[N][26],sum[N],cnt;

vector<int>node[N];
 
void insert_word(int id)
{
	int len=strlen(s);
	int pos=0;
	for(int i=0;i<len;i++)
	{
		int to=s[i]-'A';
		if(!trie[pos][to])
			trie[pos][to]=++cnt;
		pos=trie[pos][to];
	}
	::id[id]=pos;
}
 
void getfail()
{
	queue<int>q;
	for(int i=0;i<26;i++)
	{
		if(trie[0][i])
		{
			fail[trie[0][i]]=0;
			q.push(trie[0][i]);
		}
	}
	while(!q.empty())
	{
		int cur=q.front();
		q.pop();
		for(int i=0;i<26;i++)
		{
			if(trie[cur][i])
			{
				fail[trie[cur][i]]=trie[fail[cur]][i];
				q.push(trie[cur][i]);
			}
			else
				trie[cur][i]=trie[fail[cur]][i];
		}
	}
}

void build()
{
	for(int i=1;i<=cnt;i++)
		node[fail[i]].push_back(i);
}

void dfs(int u)
{
	for(auto v:node[u])
	{
		dfs(v);
		sum[u]+=sum[v];
	}
}

int main()
{
#ifndef ONLINE_JUDGE
//  freopen("data.in.txt","r",stdin);
//  freopen("data.out.txt","w",stdout);
#endif
//  ios::sync_with_stdio(false);
	int n,m,q;
	scanf("%d%d%d",&n,&m,&q);
	for(int i=1;i<=n;i++)
		scanf("%s",maze[i]+1);
	for(int i=1;i<=q;i++)
	{
		scanf("%s",s);
		insert_word(i);
	}
	getfail();
	for(int i=1;i<=n;i++)
	{
		int pos=0;
		for(int j=1;j<=m;j++)
		{
			int pos1=pos,pos2=0;
			for(int k=i;k<=n;k++)
			{
				pos1=trie[pos1][maze[k][j]-'A'];
				pos2=trie[pos2][maze[k][j]-'A'];
				sum[pos1]++;
				sum[pos2]--;
				if(i==1)
					sum[pos2]++;
			}
			pos=trie[pos][maze[i][j]-'A'];
		}
	}
	build();
	dfs(0);
	for(int i=1;i<=q;i++)
		printf("%d\n",sum[id[i]]);



























   return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_45458915/article/details/108318081
今日推荐