[Cqoi2014]通配符匹配:哈希+动态规划

前置知识:字符串哈希

题目在这

P3167 [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\)

思路+部分分代码+AC代码

首先这道题前面的字符能否匹配上直接影响到后面的状态,这不就是DP嘛!

还有个需要思考的问题,通配符是无法直接比较的,怎么办呢?我们可以分段处理,遇到通配符就断开,而遇到通配符时肯定能匹配上。

接下来考虑转移,我们按通配符分组了,通配符不超过十个,那么我们可以设f[i][j]=0/1,表示文本串的前j个字符能否匹配上前i段通配符串。为什么这么设置呢?回看我们最开始想到的,dp是为了用前面字符串能否匹配来判断后面的,本问题中实际上是用来减少时间复杂度的,看代码会更清晰一些。

第一版(90pts):

#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long//hash用的 
using namespace std;
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<<1)+(x<<3)+(ch-'0');ch=getchar();}return x*f;}inline void write(int x){if(x<0)x*=-1,putchar('-');if(x>9)write(x/10);putchar(x%10+'0');return;}inline int max(int x,int y){return (x<y)?y:x;}inline int min(int x,int y){return (x<y)?x:y;}
//一些优化(不重要,可略过) 
const int N=1e5+10,P=131; 
ull p[N],h1[N],h2[N];
int n,f[12][N],id[N],num;//id数组用来记录通配符的位置 
char s[N],t[N];
ull get(int l,int r,ull h[])//获得字串hash 
{
	return h[r]-h[l-1]*p[r-l+1];
}
signed main()
{
	p[0]=1;//不要忘记初始化 
	for(int i=1;i<=N;i++)p[i]=p[i-1]*P;
	cin>>s+1;//s+1代表字符下标从1开始 
	n=read();
	int len=strlen(s+1);
	s[++len]='?';//如果最后一个不是?在下面循
//环求h1的时候最后一段不会算上,手动模拟一下就懂啦 
	for(int i=1;i<=len;i++)
		if(s[i]=='?'||s[i]=='*') id[++num]=i;
		//保证最后的id[num]是我们补的最后一个? 
	for(int i=1;i<=num;i++)
		for(int j=id[i-1]+1;j<=id[i]-1;j++)//从上一个通配符以后 
			h1[i]=h1[i]*P+s[j];//到下一个通配符之前是一段 
	//h1[i]代表第i段的哈希值 
	while(n--)
	{	
		memset(f,0,sizeof(f));
		f[0][0]=1;//初始化,dp必不可少的一趴 
		cin>>t+1;
		int siz=strlen(t+1);
		t[++siz]='a';//因为上文多加了一个?这里我们相应地补上任意一个字母即可
		for(int i=1;i<=siz;i++)
			h2[i]=h2[i-1]*P+t[i];
		for(int i=0;i<num;i++)
			for(int j=0;j<=siz;j++)
			{
				if(f[i][j]==0)continue;//这就是dp的主要用途
//因为我们要用现在的i来推i+1的状态,如果这个j连i都匹配不上,那i+1必然也不行
				if(s[id[i+1]]=='?')//下一个通配符是'?' 
				{
					if(h1[i+1]!=get(j+1,j+id[i+1]-id[i]-1,h2))continue;//匹配不上 
					f[i+1][j+id[i+1]-id[i]]=1;//匹配上了,不用-1是因为?占一个下标,且是一定可以被匹配上的 
				}
				else
				{
					if(h1[i+1]!=get(j+1,j+id[i+1]-id[i]-1,h2))continue;
					for(int z=j+id[i+1]-id[i]-1;z<=siz;z++)f[i+1][z]=1;//不同点就是*可以是任意多个字符,i+1段后面的都可以被匹配
				}
			}
		if(f[num][siz])printf("YES");
		else printf("NO");
		putchar('\n');
	}
	return 0;
}

哈哈,惊讶地发现T了一点,距离AC只有yi步之遥.显然这个时间复杂度还不够优秀,继续想想哪里可以优化吧( '-' ).请先理解上面代码.

#include<bits/stdc++.h>
#define ull unsigned long long
using namespace std;
const int maxn=1e5+10;
const ull P=131;
ull p[maxn],h1[maxn],h2[maxn];
int q,f[12][maxn],id[maxn],num,m,n;
char s[maxn],t[maxn];
ull get(int l,int r,ull h[])
{
	if(r<l)return 0;
	return h[r]-h[l-1]*p[r-l+1];
}
int main()
{
	p[0]=1;
	for(int i=1;i<=100000;i++)p[i]=p[i-1]*P;
	scanf("%s%d",s+1,&q);
	n=strlen(s+1);s[++n]='?';
	for(int i=1;i<=n;i++) if(s[i]=='*'||s[i]=='?') id[++num]=i;
	
	for(int i=1;i<=num;i++)
		for(int j=id[i-1]+1;j<=id[i]-1;j++)
			h1[i]=h1[i]*P+s[j];
	
	while(q--)
	{	
		scanf("%s",t+1);m=strlen(t+1);t[++m]='k';
		memset(f,0,sizeof(f));
		f[0][0]=1;
		
		for(int i=1;i<=m;i++)h2[i]=h2[i-1]*P+t[i];
		for(int i=0;i<num;i++)
			for(int j=0;j<=m;j++)
			{
				if(f[i][j]==0)continue;
				if(f[i][j]==2)f[i][j+1]=2;//2是*这种情况对后面的一个延时标记,省去了for循环的复杂度 
				if(h1[i+1]!=get(j+1,j+id[i+1]-id[i]-1,h2))continue;//因为不管通配符是?还是*都要先判断,直接写在前面 
				if(s[id[i+1]]=='?')f[i+1][j+id[i+1]-id[i]]=1;
				else f[i+1][j+id[i+1]-id[i]-1]=2;
			}
		if(f[num][m])printf("YES");
		else printf("NO");
		putchar('\n');
	}
	return 0;
}

应该很详细了吧,如果有错误或者不理解的地方欢迎评论区指出哦( ^ _ ^ )!

参考博客(https://blog.csdn.net/jziwjxjd/article/details/113795426?spm=1001.2014.3001.5506)

码字不易,点个赞再走呗(卖萌)

posted @ 2025-03-29 20:30  Crab2016  阅读(24)  评论(0)    收藏  举报