抄卡组(HNOI2014)
抄卡组(HNOI2014)
题目描述
给出\(n\)个字符串,其中含有通配符\(*\),通配符可以为长度大于等于\(0\)的任意字符串。要求判断这\(n\)个字符串是否有可能为同一个字符串。
输入
第一行包含一个正整数 \(T\),表示了数据组数。
接下来包含 \(T\) 组数据:
每组数据的第一行是一个正整数 \(N\),表示该组数据的字符串个数。
接下来 \(N\) 行,每行一个字符串,字符串仅包含小写字母、数字、通配符 *
思路
这题有个比较玄学的结论:如果该n个字串的前后缀都能够匹配(即第一个/最后一个通配符之前/之后的部分),且中间至少有一个通配符,那么肯定是可以满足的。此处不给出详细证明,应该感性思考一下,都是没有问题的。
那么计算前后缀的规则比较简单(复杂):如果当前的前缀匹配超出了标准前缀,那么就把标准前缀更新到当前前缀,若匹配失败则返回\(0\). 后缀同理即可。
bool get(vector<char>str){
int i=0;
len=(int)str.size();
while(i<len&&str[i]!='*'){
if(i>(int)pre.size()-1)pre.pb(str[i]);
else if(pre[i]!=str[i])return false;
i++;
}
i=0;
while(i<len&&str[len-i-1]!='*'){
if(i>(int)suf.size()-1)suf.pb(str[len-i-1]);
else if(str[len-i-1]!=suf[i])return false;
i++;
}
return true;
}
接下来就是存在没有通配符的字串的情况,这个时候其实就是把一个没有通配符的串拿出来(这个串就相当于最终卡组了),此处叫它为\(p\)串,那么只要拿另外的\(n-1\)个串与他进行\(KMP\)匹配即可。
怎么\(KMP\)呢?只要把所有\(n-1\)个串按通配符分成若干个子串,然后只要在不改变每个子串的先后顺序,在\(p\)中找到对应的子串即可。
abaaab
*ba*a*
如以上两串,若第一个为\(p\)串,那么以下的两个子串 \(ba\) 和 \(a\),可以分别在\(p\)串中得到匹配,同时匹配的先后顺序保持不变。
不过同时,也要满足前后缀匹配(其实也可以在\(KMP\)中特判前后缀,但个人感觉比较麻烦),可以拿第一种情况方法照搬过来,为什么要匹配前后缀呢?拿这组数据就知道了\(abaaab\),\(ba*ab\),虽然能匹配但不合法。
那么综合以上两种情况就得到最后解发了,总结一下就是:判前缀后缀,若有无通配符的字串,就再将每个其他字符串再KMP匹配。上代码(奇丑无比):
代码
#include<bits/stdc++.h>
#define FOR(i,l,r) for(int i=l,i##R=r;i<=i##R;i++)
#define DOR(i,r,l) for(int i=r,i##L=l;i>=i##L;i--)
#define loop(i,n) for(int i=0,i##R=n;i<i##R;i++)
#define sf scanf
#define pf printf
#define mms(a,x) memset(a,x,sizeof a)
#define pb push_back
using namespace std;
typedef long long ll;
typedef long double db;
template<typename A,typename B>inline void chkmax(A &x,const B y){if(x<y)x=y;}
template<typename A,typename B>inline void chkmin(A &x,const B y){if(x>y)x=y;}
const int N=1e5+5;
int n;
vector<char>str[N],pre,suf;
bool legal(char c){return (c>='a'&&c<='z')||(c>='0'&&c<='9')||c=='*';}
bool read(int x){
char c;bool flag=0;str[x].clear();
while(c=getchar(),!legal(c));
str[x].pb(c),flag|=(c=='*');
while(c=getchar(),legal(c))str[x].pb(c),flag|=(c=='*');
return flag;
}
int len;
bool fpre,fsuf;
bool get(vector<char>str){
int i=0;
len=(int)str.size();
while(i<len&&str[i]!='*'){
if(i>(int)pre.size()-1){
pre.pb(str[i]);
}
else if(pre[i]!=str[i])return false;
i++;
}
i=0;
while(i<len&&str[len-i-1]!='*'){
if(i>(int)suf.size()-1){
suf.pb(str[len-i-1]);
}
else if(str[len-i-1]!=suf[i])return false;
i++;
}
return true;
}
struct Pt2{
int Next[N*20];
int lst,id;
char tmp[N*20];
void get_nxt(char str[],int len){
int j=0;
FOR(i,2,len){
while(j&&str[i]!=str[j+1])j=Next[j];
j+=(str[i]==str[j+1]);
Next[i]=j;
}
}
bool KMP(int len){
int j=0;
FOR(i,lst,str[id].size()-1){
while(j&&str[id][i]!=tmp[j+1])j=Next[j];
j+=(str[id][i]==tmp[j+1]);
if(j==len){lst=i+1;return 1;}
}
return 0;
}
bool check(vector<char>str){
lst=0;
loop(i,str.size()){
if(str[i]!='*'){
int ct=0;
while(i<(int)str.size()&&str[i]!='*')tmp[++ct]=str[i],i++;
tmp[ct+1]='\0';
get_nxt(tmp,ct);
if(!KMP(ct))return false;
i--;
}
}
return true;
}
void solve(){
id=0;
sf("%d",&n);
FOR(i,1,n)if(!read(i))id=i;
fpre=fsuf=0;
pre.clear(),suf.clear();
bool flag=0;
FOR(i,1,n)if(!flag&&!get(str[i]))flag=1;
if(flag){puts("N");return;}
if(id){
FOR(i,1,n){
if(i==id)continue;
if(!check(str[i])){puts("N");return;}
}
}
puts("Y");
}
}Pt_2;
int main(){
freopen("hs.in","r",stdin);
freopen("hs.out","w",stdout);
int T;
sf("%d",&T);
while(T--)Pt_2.solve();
return 0;
}