通配符匹配|dfs,hash|题解

[CQOI2014] 通配符匹配

题目描述

几乎所有操作系统的命令行界面(CLI)中都支持文件名的通配符匹配以方便用户。最常见的通配符有两个,一个是星号(*),可以匹配 0 个及以上的任意字符:另一个是问号(?),可以匹配恰好一个任意字符。现在需要你编写一个程序,对于给定的文件名列表和一个包含通配符的字符串,判断哪些文件可以被匹配。

输入格式

第一行是一个由小写字母和上述通配符组成的字符串。第二行包含一个整数 \(n\),表示文件个数。接下来 \(n\) 行,每行为一个仅包含小写字母字符串,表示文件名列表。

输出格式

输出 \(n\) 行,每行为 YESNO,表示对应文件能否被通配符匹配。

样例 #1

样例输入 #1

*aca?ctc
6
acaacatctc
acatctc
aacacatctc
aggggcaacacctc
aggggcaacatctc
aggggcaacctct

样例输出 #1

YES
YES
YES
YES
YES
NO

提示

对于 \(100 \%\) 的数据

  • 字符串长度不超过 \(100000\)
  • \(1 \le n \le 100\)
  • 通配符个数不超过 \(10\)

解析

这个题大意是让我们用一个模式字符串(带一些特殊符号的),让我们去对应匹配它给出的各个字符串.
? 可以替换一个字符,* 可以替换一堆字符,让你看看这些输入的串中有哪些可以匹配,哪些不可以.
然后就想想思路,貌似挺简单.
1.写这个题的时候还没开KMP,现在看起来KMP是可以解决的,把模式串根据通配符拆开,看各个串是否能够匹配是可行的.
2.如果写最基础的dfs貌似也很简单,就是对于* 一个一个字符试过去,对于? 跳过当前匹配,而且很好的是它不会超时,但当时有人卡掉了DFS.
3.那就可以用dp了,将阶段与状态构思一下,让每个分串作为阶段(只有上一个串匹配,才可匹配下一个串),让字符串的总长作为状态,跟上面一样让通配符作为分解将串分开,一个分串(长度为sublen)的hash值如果能够对应上我们输入串的某个部分[j,i],(i-j+1=sublen),当出现匹配的时候就进行串上某位置到另一位置的转移(i-sublen->i),到最后如果串尾状态为1,则为yes,否则为no.
然而dfs怎么能够遇到一点小小的困难就放弃呢.
既然问题在于超时,那就想想剪枝吧,我们之前的dfs是一个个字符匹配过去,现在我们换成用分串的hash值匹配.
也就是说我们这么做,dfs的层数最多是11层(因为通配符最多10个).
那再想想这个dfs在通配符处如何处理.
我们用一个数组(此处用的是br)保存通配符种类,* 为0,? 为1(主要因为我用的三目^$* $),再以此分开处理就好.
怎么处理呢,如果遇到的是? ,就让它在待匹配串上跳过一位,让该? 后缀的分串去和待匹配串剩下的部分匹配.
如果遇到的是* ,就让它后缀的分串往后不断去匹配各个串,直到我们无可匹配.
还是不放心复杂度,万一就超时了呢,所以我们再加一个剪枝.
假设我们这个串有两个* 而第一个* 的后缀串已经对应上了待匹配串的一部分,但是第二个* 和剩下的位置均无法匹配,那么我们如果以我们朴素搜索策略,会再次返回到第一个* 的位置继续尝试.
但是仔细分析会发现,如果我们已经到达了最后一个* 的位置,它在第一次被尝试时不行,之后也一定不行,因为我们尝试的位置是我们第一次已经尝试过的位置.
所以说在第一次达到最深的* 尝试完所有的可能匹配位置后这个串没有被匹配,那么就用flag标记上,直接return false.
这样就100%不超时了.
其实还有一个优化,用待匹配串的剩余长度与剩余分串的总长进行比较(在代码中用est与st数组存储分串与串长,nowlen表示待匹配串长度),如果剩余长度比当前总长小,就return 0;
然而拥有废物码风的我带上这个剪枝就炸了
后记:如果细心观察的话,其实我们的dfs在这样的匹配方式下就是进行了优化的dp,而且省去了一些无用的比较,是会更快些的.
至此结束了?然而还是有个人将自然溢出卡没了.
所以就补上了带模数hash(貌似是自己第一次写带模数hash,要把格式记住.)
码风很差,你忍一下.

正解代码

#include<bits/stdc++.h>
#define qr qr()
#define llu unsigned long long
#define ll long long
using namespace std;

const int N=1e5+50000,base=131,mod=1001992007;
int n,len,nowlen,top,est[11],st[11];
//双模数hash. 
llu ha1[N],ha2[N],opha1[12],opha2[12],p1[N],p2[N],ebr[N];
char s[N],cop[11],a[N];
bool br[11],fl,flag;
llu gethash1(int l,int r)
{
	return ha1[r]-ha1[l-1]*p1[r-l+1];
}
ll gethash2(int l,int r)
{
	return (ha2[r]-ha2[l-1]*p2[r-l+1]%mod+mod)%mod;
}
bool dfs(int now,int l)
{
//	printf("%d(%d&&%d)",now,l,br[now]);
	//边界条件有多种可能:
	//1.最后所有分串都被匹配,待匹配串刚好用完 return 1. 
	//2.最后所有分串都被匹配,但是待匹配串没被用完 return 0.
	//3.最后剩下的是一个*,可以匹配剩下的所有字符 return 1. 
	if(now>top&&l>nowlen)return 1;
	else if(now>top&&l<=nowlen)return 0;
	else if(!br[now]&&now==top)return 1;
	if(br[now])
	{
		if(st[now]==0) return dfs(now+1,l+br[now+1]);//特判串长为0(两个通配符挨着) 
//		printf("%d,%llu %llu\n",st[now],gethash1(l,l+st[now]-1),opha1[now]);
		if(gethash1(l,l+st[now]-1)==opha1[now]&&gethash2(l,l+st[now]-1)==opha2[now]) 
			return dfs(now+1,l+st[now]+br[now+1]);
		else return 0;
	}
	else
	{
		if(!st[now])
		{//我的写法没有将每个通配符单作一个串处理. 
		//所以每个地方都要特判一个串是否为空串不然hash会越界而且无法处理... 
		// 最复杂的一集. 
			while(!flag&&nowlen>=l-1+est[now]+ebr[now])
			{
//				printf("%d:(%d|%d)",now,l,st[now]);
				if(dfs(now+1,l+br[now+1]))return 1;
				++l;
			}
		} else
		{
			int r=l+st[now]-1;
			while(!flag&&nowlen>=r)
			{
//				printf("%d:(%d,%d|%d)",now,l,r,st[now]);
//				printf("%llu,%llu\n",gethash1(l,r),opha1[now]);
				if(gethash1(l,r)==opha1[now]&&gethash2(l,r)==opha2[now])//,gethash2(l,r),opha2[now]
					if(dfs(now+1,r+1+br[now+1]))return 1;
				++l,++r;
			}
		}
		flag=1;
		return 0;
	}
}
void init()
{
	//prework 
	char op;
	p1[0]=1,p2[0]=1,br[0]=1;
	for(int i=1;i<=(1e5);++i)
	{
		p1[i]=p1[i-1]*base;
		p2[i]=p2[i-1]*base%mod;
	}
	while(1)
	{
		op=getchar();
		if(op==' '||op=='\n')
		{
			st[top]=len-est[top-1];
			break;
		}
		if(!(op^'*')||!(op^'?'))
			st[top]=len-((top>=1)?est[top-1]:0),len+=((op^'*')?1:0),est[top]=len,br[++top]=(op^'*');
		else opha1[top]=opha1[top]*base+op,opha2[top]=(opha2[top]*base%mod+op)%mod,++len;
	}
	for(int i=top;i;--i)ebr[i]=ebr[i+1]+br[i];
//	for(int i=0;i<=top;++i)printf("%d ",st[i]);
//	putchar('\n');
//	for(int i=0;i<=top;++i)printf("%d ",br[i]);
//	putchar('\n');
//	for(int i=0;i<=top;++i)printf("%llu ",opha1[i]);
	scanf("%d",&n);
	//剪枝dfs? 
	for(int i=1;i<=n;++i)
	{
		flag=0;
		scanf("%s",s+1);
		nowlen=strlen(s+1);
		if(nowlen<len)
		{ 
			printf("NO\n");
			continue;
		}
		for(int now=1;now<=nowlen;++now)
			ha1[now]=ha1[now-1]*base+s[now],ha2[now]=((ha2[now-1]*base)%mod+s[now])%mod;
		//超时怎么办. 
		//可行性剪枝呗,出现一个*后,后面的串无法匹配,就break.近似极限复杂度为O(n*N)绰绰有余. 
		if(dfs(0,1))printf("YES\n");
		else printf("NO\n");
	}
}
int main()
{
	init();
	return 0;
}
/*
*aca?ctc*
10
acaacatctc
acatctc
aacacatctc
aggggcaacacctc
aggggcaacatctc
aggggcaacctct
aggggcaacctct
aggggcaacatctcvvv
acaacaacaacaacaacaacaacaacaacaacaacaacaacaacaacaacaacaacaacaacacaactc
cacatctcc
rbwhqbyehwrqlrzw?*?wy*?*pybzyllklojxjequqqpkr??yxnqwxrdszz*aca?ctc
2
rbwhqbyehwrqlrzwzwwytpybzyllklojxjequqqpkrhhyxnqwxrdszzacaacatctc
rbwhqbyehwrqlrzwzwwytasfvertvetpybzyllklojxjequqqpkrhhyxnqwxrdszzacaactc
*/
posted @ 2024-04-28 16:11  SLS-wwppcc  阅读(23)  评论(0编辑  收藏  举报