LuoguP3167通配符匹配

题意

本题的意思就是给出一段带有 $ ? $ 与 \(*\) 的字符串
(在下面称为\(s\)),
$ ? $ 必须占据一个字符位置, \(*\) 可以占据任意位置,
求下面给出几段(在下面称为\(ss\))中能够匹配的字符串。

思路


前言

本题最可恶的一点是,如果s的第一段/最后一段是字符,那么ss中最开始/最后也必须是一样的字符。

另外,本题我使用了hash + 前缀,如果不太熟悉的话可以了解一下~传送门
举个栗子~

如样例,s为*aca?ctc,我们就可以将它分为

\(*\) ,aca, $ ? $ ,ctc 共四段。

对于每一段,我们用一个add数组来判断它的种类,

用co记录它的段数,字符串对应2,\(*\) 对应1,$ ? $ 对应0:


	scanf("%s",s+1);lens=strlen(s+1);
	if(s[1]<'a'||s[1]>'z')co=0;else add[1]=2;
	for(int i=1;i<=lens;i++)
	{
		if(s[i]>='a'&&s[i]<='z')
		{
			len[co]++;
			f[co]=f[co]*131+s[i];
		}
		else if(s[i]=='*') {add[++co]=1;if(i!=lens&&s[i+1]>='a'&&s[i+1]<='z')add[++co]=2;}
		else{co++;if(i!=lens&&s[i+1]>='a'&&s[i+1]<='z')add[++co]=2;}
	}

注意,由于开头字符不好判断,所以这里加了一个特判,
来记录开头是字符串的情况。

判断

预处理好了,那么,如何进行判断呢?

这里,我用了 $ doit $ 与 \(ask\) 两个自定义函数,

doit用来对可以自由匹配的字符种类进行判断处理,

ask用来对“ ? ”后的字符进行判断处理。

两个函数中都使用了key,k两个参数,
key记录了到哪个字符(指针),k记录了到第几段。

对于doit:


void doit(int key,int k)
{
	if(k>co){can=1;return;}//段数数完了,说明满足条件;
	if(add[k]==2)//字符
	{
		for(int i=key+len[k]-1;i<=le;i++)
		if(ff[i]-ff[i-len[k]]*p[len[k]]==f[k])//寻找符合的解进行doit;
		{doit(i+1,k+1);if(can)return;}
		return;
	}
	if(add[k]==1)//星号
	{
		doit(key,k+1);
		if(can)return;
		return;
	}
	if(add[k]==0)//问号
	{
		ask(key+1,k+1);return;
	}
}

对于ask:


void ask(int key,int k)
{
	if(k>co){can=1;return;}
	if(add[k]==2)
	{
		if(ff[key+len[k]-1]-ff[key-1]*p[len[k]]==f[k])doit(key+len[k],k+1);
	}
	else
	{
		if(add[k]==1){if(add[k+1]==0){
        for(int i=key+2;i<=le;i++)
      	  if(ff[i+len[k+2]-1]-ff[i-1]*p[len[k+2]]==f[k+2])doit(i+len[k+2],k+3);}
        doit(key,k+1);}//星号,判断下一个是不是问号
        如果是问号就要找到能够匹配的子串进行doit;
		else ask(key+1,k+1);//问号继续ask;
	}
}

收尾

到这里,万事俱备,只欠东风,进行我们华丽的结束吧~

接下来就该读入与判断了:

	p[0]=1;
	for(int i=1;i<=100000;i++)p[i]=p[i-1]*131;
	int n=read();
	while(n--)
	{can=0;
	scanf("%s",ss+1);le=strlen(ss+1);
	for(int i=1;i<=le;i++)ff[i]=ff[i-1]*131+ss[i];
	if(add[1]==2)if(ff[len[1]]-ff[0]*p[len[1]]!=f[1]){printf("NO\n");continue;}//前言中说的特殊情况
	if(add[co]==2)if(ff[le]-ff[le-len[co]]*p[len[co]]!=f[co]){printf("NO\n");continue;}
	doit(1,1);
	if(can)printf("YES\n");else printf("NO\n");
	}
    return 0;

CODE


#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
#define ull unsigned long long
int read(){int x=0;char ch=getchar();while(ch<'0'||ch>'9')ch=getchar();while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();return x;}
char s[100005],ss[100005];
int lens,len[25],co=1,le,k,key,cnt,add[25];bool can;
ull f[25],ff[100010],p[100010];
void ask(int,int);
void doit(int,int);
void ask(int key,int k)
{
	if(k>co){can=1;return;}
	if(add[k]==2)
	{
		if(ff[key+len[k]-1]-ff[key-1]*p[len[k]]==f[k])doit(key+len[k],k+1);
	}
	else
	{
		if(add[k]==1){if(add[k+1]==0){for(int i=key+2;i<=le;i++)if(ff[i+len[k+2]-1]-ff[i-1]*p[len[k+2]]==f[k+2])doit(i+len[k+2],k+3);}doit(key,k+1);}
		else ask(key+1,k+1);
	}
}
void doit(int key,int k)
{
	if(k>co){can=1;return;}
	if(add[k]==2)
	{
		for(int i=key+len[k]-1;i<=le;i++)
		if(ff[i]-ff[i-len[k]]*p[len[k]]==f[k])
		{doit(i+1,k+1);if(can)return;}
		return;
	}
	if(add[k]==1)
	{
		doit(key,k+1);
		if(can)return;
		return;
	}
	if(add[k]==0)
	{
		ask(key+1,k+1);return;
	}
}
int main()
{
	scanf("%s",s+1);lens=strlen(s+1);
	if(s[1]<'a'||s[1]>'z')co=0;else add[1]=2;
	for(int i=1;i<=lens;i++)
	{
		if(s[i]>='a'&&s[i]<='z')
		{
			len[co]++;
			f[co]=f[co]*131+s[i];
		}
		else if(s[i]=='*') {add[++co]=1;if(i!=lens&&s[i+1]>='a'&&s[i+1]<='z')add[++co]=2;}
		else{co++;if(i!=lens&&s[i+1]>='a'&&s[i+1]<='z')add[++co]=2;}
	}
	p[0]=1;
	for(int i=1;i<=100000;i++)p[i]=p[i-1]*131;
	int n=read();
	while(n--)
	{can=0;
	scanf("%s",ss+1);le=strlen(ss+1);
	for(int i=1;i<=le;i++)ff[i]=ff[i-1]*131+ss[i];
	if(add[1]==2)if(ff[len[1]]-ff[0]*p[len[1]]!=f[1]){printf("NO\n");continue;}
	if(add[co]==2)if(ff[le]-ff[le-len[co]]*p[len[co]]!=f[co]){printf("NO\n");continue;}
	doit(1,1);
	if(can)printf("YES\n");else printf("NO\n");
	}
	return 0;
}

结语

这道题我也调了有好久了,甚至都有些舍不得了,所以写下了本篇题解,也是我的第一篇题解

如果对你有帮助,求个赞qwq

posted @ 2021-05-28 16:43  letitdown  阅读(61)  评论(0编辑  收藏  举报