【题解】Codeforces149E. Martian Strings 前缀函数

字符串板刷 21 / 57 21/57 21/57

给定一个长为 n ( 1 e 5 ) n(1e5) n(1e5)的字符串 S S S,然后给定 m ( 100 ) m(100) m(100)个长度不超过 1000 1000 1000的模式串。

考虑所有的 T = S [ a . . b ] + S [ c . . d ] , c > b T=S[a..b]+S[c..d],c>b T=S[a..b]+S[c..d],c>b,问有几个模式串在至少一个 T T T中出现.


对于每个模式串独立处理。

定义后缀函数:模式串和文本串全部反向后的前缀函数。

首先求得每个模式串的前缀函数 p r e f pref pref,再求得后缀函数 s u f f suff suff.

如果 S S S中存在位置 i < j i<j i<j,满足 p r e f [ i ] + s u f f [ j ] = l e n pref[i]+suff[j]=len pref[i]+suff[j]=len,其中 l e n len len是字符串长度,那么显然这个字符串会在一个 T T T中出现。

有一个显然(我为什么没想到 )的贪心性质是:我们只需要计算出pref的前缀最大值 m a x _ p r e f [ i ] max\_pref[i] max_pref[i],以及suff的后缀最大值 m a x _ s u f f [ i ] max\_suff[i] max_suff[i],然后看 m a x _ p r e f [ i ] + m a x _ s u f f [ i + 1 ] max\_pref[i]+max\_suff[i+1] max_pref[i]+max_suff[i+1]是否大于 l e n len len即可。


总结:

  1. 认真读题
  2. 当出现了 j j j的前缀函数值时,之前一定出现过 1 , 2 , . . . . j − 1 1,2,....j-1 1,2,....j1
  3. 长度为 1 1 1的字符串无法被分割。
/* LittleFall : Hello! */
#include <bits/stdc++.h>
using namespace std; using ll = long long; inline int read();
const int M = 100016, MOD = 1000000007;

char tex[M], pat[M];
int fail[M], pref[M], suff[M];
void make_fail()
{
	for(int i=1, j=0; pat[i]; ++i)
	{
		while(j && pat[i]!=pat[j]) j=fail[j-1];
		fail[i] = pat[i]==pat[j] ? ++j : 0;
	}
}
void search(int *arr) //求文本串对模式串的前缀函数
{
	for(int i=0, j=0; tex[i]; ++i)
	{
		while(j && tex[i]!=pat[j]) j=fail[j-1];
		arr[i] = tex[i]==pat[j] ? ++j : 0;
	}
	for(int i=1; tex[i]; ++i)
		arr[i] = max(arr[i], arr[i-1]);
}
bool check(int n)
{
	int m = strlen(pat);
	if(m==1) return 0;
	make_fail(); search(pref);
	reverse(tex, tex+n); reverse(pat, pat+m);
	make_fail(); search(suff);
	reverse(tex, tex+n); reverse(pat, pat+m);
	reverse(suff, suff+n);

	for(int i=0; i<n-1; ++i)
		if(pref[i]+suff[i+1]>=m)
			return 1;
	return 0;
}
int main(void)
{
	#ifdef _LITTLEFALL_
	freopen("in.txt","r",stdin);
    #endif

	scanf("%s", tex);
	int n = strlen(tex), q = read(), ans = 0;
	for(int i=1; i<=q; ++i)
	{
		scanf("%s", pat);

		if(check(n)) ++ans;
	}
	printf("%d\n",ans );


    return 0;
}

inline int read(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值