CF-GYM101741K. Consistent Occurrences

CF-GYM101741K. Consistent Occurrences

题意:给你一个长度为(n)的串(s),以及(m)次询问,每次给出一个串(t),询问(t)(s)中最多出现了多少次(出现不能重叠)。

联想到前一题,我们可以将询问建树。

我们可以自然地想到,当匹配成功后,直接跳回根节点(就像kmp一样)。但是,由于是多串匹配,该算法假掉了。

这时候,我们看一道题:凌乱的yyy / 线段覆盖

有人说,一道橙题,有什么好看的?

思想可以借鉴。

首先,每个匹配出来的出现位置,就相当于一条线段。而由于是顺序匹配,就默认左端点有序。因此,我们对于每个文本串,在(fin)这个(vector)内,不仅要存编号(id),也要存上一次出现的右端点(las)与文本串长度(len)

则如果对于当前匹配的位置(i),有(lasle i),则这条线段就可以被覆盖上。

(merge)代码:

void merge(){
	int x=1;
	for(int i=0;i<S;i++){
		x=t[x].ch[s[i]-'a'];
		for(int y=x;y!=1;y=t[y].fail)for(int j=0;j<t[y].fin.size();j++)if(t[y].fin[j].las<=i)res[t[y].fin[j].id]++,t[y].fin[j].las=i+t[y].fin[j].len;
	}
}

(记录文本串长度是为了方便推出右端点)

并且,由于这题实在不好拓扑,就只写了暴力跳(fail)的程序(反正也能过)。

总代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,S,res[100100],cnt=1;
char s[100100],ss[100100];
struct Query{
	int id,las,len;
	Query(int x=0,int y=0,int z=0){
		id=x,las=y,len=z;
	}
};
struct AC_Automaton{
	int ch[26],fail;
	vector<Query>fin;
}t[100100];
void ins(int id){
	int x=1;
	for(int i=0;i<S;i++){
		if(!t[x].ch[ss[i]-'a'])t[x].ch[ss[i]-'a']=++cnt;
		x=t[x].ch[ss[i]-'a'];
	}
	t[x].fin.push_back(Query(id,0,S));
}
queue<int>q;
void build(){
	for(int i=0;i<26;i++){
		if(t[1].ch[i])t[t[1].ch[i]].fail=1,q.push(t[1].ch[i]);
		else t[1].ch[i]=1;
	}
	while(!q.empty()){
		int x=q.front();q.pop();
		for(int i=0;i<26;i++){
			if(t[x].ch[i])t[t[x].ch[i]].fail=t[t[x].fail].ch[i],q.push(t[x].ch[i]);
			else t[x].ch[i]=t[t[x].fail].ch[i];
		}
	}
}
void merge(){
	int x=1;
	for(int i=0;i<S;i++){
		x=t[x].ch[s[i]-'a'];
		for(int y=x;y!=1;y=t[y].fail)for(int j=0;j<t[y].fin.size();j++)if(t[y].fin[j].las<=i)res[t[y].fin[j].id]++,t[y].fin[j].las=i+t[y].fin[j].len;
	}
}
int main(){
	scanf("%d%d",&n,&m);
	scanf("%s",s);
	for(int i=1;i<=m;i++)scanf("%s",ss),S=strlen(ss),ins(i);
	build(),S=n;
	merge();
	for(int i=1;i<=m;i++)printf("%d
",res[i]);
	return 0;
}
原文地址:https://www.cnblogs.com/Troverld/p/12781173.html