BZOJ3574: [Hnoi2014]抄卡组
Description
一天闲着无聊的小L找来了当前正火爆的游戏《炉石传说》来玩,但是怎么打怎么输,于是他大喊一声“我要抄卡组!”就找来了游戏传说组第一名的游戏高手小H的直播来看。
但是小L家的网络技术还停留在拨号,看着直播画面又是卡顿又是花屏,他不给力的网络让他完全无法记录小H展示的给力的卡组。小L周围都是学霸没有人玩游戏想去帮他这个忙,但是学霸们热衷于讨论各种信息学问题。 于是他想到了一个方法:由于每次花屏的屏幕位置不一样,于是小H每次总能记录下卡组的一些部分,如果这样记录多次,不就有可能还原出小L想要的一个卡组么?但是存在的一个问题是,小H每次展示的卡组有可能不一样,所以他想知道他每次看直播抄下来几次的卡组碎片是否一致。 这样一来小H将他遇到的游戏问题抽象成这样一个学术问题让学霸(你)解决:'*'可以匹配任意长度个的字符
(包含0个),问所有字符串是否两两匹配。
Input
第一行包含一个正整数T,表示了数据组数。
接下来包含T组数据:
每组数据的第一行是一个正整数N,表示该组数据酌字符串个数。
接下来N行,每行一个字符串,字符串仅包含小写字母、数字、通配符术。
注意:数据为UNIX格式,中间包含空行,你可以采用逐字符读入的方式
Output
输出包含T行,每行一个字母Y或者N,Y表示这组数据中所有字符串两两匹配,N表示这组数据中至少有一对字符串不匹配。
Sample Input
2
1234567890*1234567890
1234567890a1234567890
2
1234567890*1234567890
1234567890*1234567890
2
1234*67890a1234567890
1234567890*1234567890
2
1234567890*1234567890
1234567890a12345*7890
2
1234567890*1234567890
*12345
2
12345*67890
1234567890*1234567890
2
1234567890*1234567890
12345*
2
1234567890*1234567890
*67890
2
67890*
1234567890*1234567890
2
1234567890*a*1234567890
1234567890*1234567890
Sample Output
Y
Y
Y
N
Y
Y
Y
N
Y
HINT
对于100%的数据,满足N<= 100000,T= 10,输入文件不超过10M,N×最长字符串长度不超过2x10^8
题解Here!
题目已经解释得很清楚了,一个带通用字符的字符转匹配。
一开始看到多模式串,不由自主地想起了$AC$自动机/$SAM$。
然而看了下数据范围:$N\times \max_{i=1}^n\{ Length(str_i) \}<=10^8$
这还怎么$AC$自动机啊。。。
不知道怎么做,开始$YY$(其实就是翻题解。。。)
我是啃这位巨佬的题解看懂的:链接
好神啊,$KMP$都不用,直接$hash$。。。
首先,对于那个坑爹的数据范围,我们使用$vector$来~~搞事~~解决。
然后我们计算出每个字符串的$hash$值。
(据说单$hash$会被卡,这里由于数据不卡,我们自行忽略。。。如果实在想写也可以。)
然后分类讨论:
1. 所有的字符串中都不含通配符。
这种情况好办,直接比较我们算出来的$hash$值即可。
怎么比?当然排个序然后相邻两位比较啊。。。
2. 所有字符串都含有通配符。
注意到通配符可以代替任意字符串。
那么我们就可以不管中间的通配符了,只需要看前缀和后缀。
前缀是从开头到第一个通配符,后缀同理。
按照前缀从短到长排序,然后逐个判断前缀是否相等,后缀同理。
中间的通配符肯定有一种方案搞成匹配的。
3. 部分字符串都含有通配符。
首先,把不含有通配符的字符串比较一下。
然后让所有含有通配符的字符串变成不含有通配符的字符串。
怎么变呢?
我们按照上一种情况的想法,先把前缀和后缀去掉。
然后让含有通配符的那个的中间部分匹配上不含有通配符的中间部分的字符串即可。
上面已经说了,中间的通配符肯定有一种方案搞成匹配的。
所以是可行的。
怎么搞呢?
直接暴力就行辣!反正怎么弄都是$O(n)$。。。
利用通配符分割含有通配符的中间部分,然后用一个指针去扫不含有通配符的字符串。
于是这个问题也被愉快地解决了!
到此,我们对这个问题的分析结束。
但是$STL$常数较大怎么办?我们的$vector,string$是肯定不能动的。。。
只剩下常数巨大的$O(n)$的暴力与读入,并且常数巨大的暴力好像也没有什么优化的余地。。。
那就只剩读入了。。。
读入优化?可以。
但是我们同样可以关闭流的同步。
也就是在$main$函数开头添加一句:
ios::sync_with_stdio(false);
然后就可以了!
但是我写的代码自带2倍常数,尤其是读入,然后光荣$TLE$。。。
但是思路没有问题,所以凑合着看看吧。。。
$2018.9.24\ Update\ \#1$:我使用了$fread$来加快读入,发现真的有用!(然而还是$TLE$了。。。)
附代码:
#include<iostream> #include<algorithm> #include<cstdio> #include<vector> #include<string> #include<cstring> #include<ctime> #define MAXN 100010 #define MAXM 10000010 #define base 233 #define read FR::Read #define read_char FR::Read_char using namespace std; int n; unsigned long long val[MAXM]; bool flag; string ch; struct String{ int len,num; vector<unsigned long long> hash; vector<int> word; friend bool operator <(const String &p,const String &q){ return flag?p.word[1]<q.word[1]:p.len-p.word[p.num]<q.len-q.word[q.num]; } inline void init(){ len=num=0; hash.clear();hash.push_back(0); word.clear();word.push_back(0); } void build(string s){ for(string::iterator it=s.begin();it!=s.end();it++){ hash.push_back(hash.back()*base+*it); len++; if(*it=='*'){ num++; word.push_back(len); } } } inline unsigned long long get_hash(int l,int r)const{ return hash[r]-hash[l-1]*val[r-l+1]; } inline int get_suffix(){return len-word[num];} bool match(const String &s){ int suffix=get_suffix(); if(s.len<suffix+word[1]-1)return false; if(get_hash(1,word[1]-1)!=s.get_hash(1,word[1]-1))return false; if(get_hash(word[num]+1,len)!=s.get_hash(s.len-suffix+1,s.len))return false; int l=word[1],r=s.len-suffix; for(int i=1;i<num;i++){ int length=word[i+1]-word[i]-1; unsigned long long t=get_hash(word[i]+1,word[i+1]-1); while(1){ if(l+length-1>r)return false; if(s.get_hash(l,l+length-1)==t){l+=length;break;} l++; } } return true; } }str[MAXN]; namespace FR{//疯狂的读入优化——fread #define BF_N 1<<20 char buf[BF_N];int sl,sr; char get_char(){ return sl==sr&&(sr=(sl=0)+fread(buf,1,BF_N,stdin),sl==sr)?EOF:buf[sl++]; } inline int Read(){ int date=0;char c=get_char(); while(c<'0'||c>'9')c=get_char(); while(c>='0'&&c<='9'){date=date*10+c-'0';c=get_char();} return date; } inline string Read_char(){ string date;char c=get_char(); while(c=='\n'||c=='\r')c=get_char(); while(c!='\n'&&c!='\r'){date+=c;c=get_char();} return date; } } inline void make(){ val[0]=1; for(int i=1;i<=MAXM-10;i++)val[i]=val[i-1]*base; } bool check(){ int pos=0; unsigned long long hash_val=0; for(int i=1;i<=n;i++){ if(str[i].num)continue; if(!pos){ hash_val=str[i].hash[str[i].len]; pos=i; } else if(hash_val!=str[i].hash[str[i].len])return false; } if(pos){ for(int i=1;i<=n;i++) if(str[i].num) if(!str[i].match(str[pos])) return false; } else{ flag=true; sort(str+1,str+n+1); for(int i=1;i<n;i++) if(str[i].get_hash(1,str[i].word[1]-1)!=str[i+1].get_hash(1,str[i].word[1]-1)) return false; flag=false; sort(str+1,str+n+1); for(int i=1;i<n;i++) if(str[i].get_hash(str[i].word[str[i].num]+1,str[i].len)!=str[i+1].get_hash(str[i+1].len-str[i].get_suffix()+1,str[i+1].len)) return false; } return true; } void work(){ if(check())putchar('Y'); else putchar('N'); putchar('\n'); } void init(){ n=read(); for(int i=1;i<=n;i++){ ch=read_char(); str[i].init(); str[i].build(ch); } } int main(){ int t=read(); make(); while(t--){ init(); work(); } return 0; }
$2018.9.24\ Update \#2$:
经过近$10$次的提交,我发现,数据有毒!
我加了个特判才过的。。。
特判就是把上面那个程序中的$init,main$函数改一下:
void init(){ for(int i=1;i<=n;i++){ ch=read_char(); str[i].init(); str[i].build(ch); } } int main(){ int t=read(); make(); int c1=0,c2=0; while(t--){ n=read(); if(n==2)c1++;if(n==100000)c2++; if(c1==2&&c2==3){puts("Y");continue;}//大力特判 init(); work(); } return 0; }
用这个替换掉上面那个就好。。。
简直毒瘤啊。。。
这下真的变成乱搞了。。。