[BZOJ]4650 优秀的拆分(Noi2016)(哈希+二分)
传送门
题解
听说大佬们这题都是用SA秒掉的
然而SA的时间复杂度的确很优秀,缺点就是看不太懂……
然后发现一位大佬用哈希华丽的过了此题,而且讲的特别清楚->这里
我们只要考虑以每一个点结尾的$AA$串的个数$u[i]$和以每一个点开头的AA串的个数$v[i]$,答案就是$\sum _{i=1}^{n-1} u[i]*v[i+1]$
那么考虑如何求出$u$和$v$呢
我们考虑一下,枚举串$A$的长度$len$,然后每隔$len$个单位设置一个关键点。不难发现,每一个长度为$len*2$的$AA$串,必定经过两个关键点
然后考虑,只要求出相邻两个关键点往前的$LCS$和往后的$LCP$,如果$LCS+LCP>=len$,就表明存在长度为$len$的$AA$串。而且不难发现,所有经过这两个关键点的长度为$len$的$AA$串,肯定是连续的!所以我们可以找到这个区间,然后用前缀和差分,就可以避免区间修改了
说了这么多,到底怎么求$LCS$和$LCP$呢?(大佬:SA+ST表不是随便过的么)嗯,没错,二分。我们二分它们的长度,然后用哈希判断是否相等。这样虽然时间复杂度比起ST表多了个$log$,但起码更看得懂……
时间复杂度是枚举$len$的调和级数,加上二分,为$O(nlog^2n)$
ps:话说我也不明白调和级数是个什么玩意儿,只要知道枚举的复杂度是$\sum _{i=1}^n \frac{n}{i} =O(nlogn)$就行了……
pps:管那么多干嘛能A不就行了么……话说这题明明纯哈希暴力就有95……某大佬讲课的时候还以这题为例嘲笑NOI近几年的出题水平(逃)
1 //minamoto 2 #include<iostream> 3 #include<cstdio> 4 #include<algorithm> 5 #include<cstring> 6 #define ll long long 7 using namespace std; 8 #define getc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++) 9 char buf[1<<21],*p1=buf,*p2=buf; 10 inline int read(){ 11 #define num ch-'0' 12 char ch;bool flag=0;int res; 13 while(!isdigit(ch=getc())) 14 (ch=='-')&&(flag=true); 15 for(res=num;isdigit(ch=getc());res=res*10+num); 16 (flag)&&(res=-res); 17 #undef num 18 return res; 19 } 20 char sr[1<<21],z[20];int C=-1,Z; 21 inline void Ot(){fwrite(sr,1,C+1,stdout),C=-1;} 22 inline void print(ll x){ 23 if(C>1<<20)Ot();if(x<0)sr[++C]=45,x=-x; 24 while(z[++Z]=x%10+48,x/=10); 25 while(sr[++C]=z[Z],--Z);sr[++C]='\n'; 26 } 27 const int N=30005,mod=3e7+7; 28 char s[N];int n; 29 ll hash[N],mo[N],u[N],v[N],ans; 30 inline ll gethash(int l,int r){ 31 ll now=hash[l]-hash[r]*mo[r-l]; 32 now%=mod,now+=mod,now%=mod; 33 return now; 34 } 35 int main(){ 36 int T=read();mo[0]=1;for(int i=1;i<=30000;++i) mo[i]=mo[i-1]*31%mod; 37 while(T--){ 38 n=0;char ch; 39 while((ch=getc())!='\n') s[++n]=ch; 40 memset(u,0,sizeof(u)),memset(v,0,sizeof(v)); 41 hash[n+1]=0; 42 for(int i=n;i;--i) (hash[i]=hash[i+1]*31+s[i]-'a'+1)%=mod; 43 for(int L=1;L*2<=n;++L){ 44 for(int i=L<<1;i<=n;i+=L){ 45 if(s[i]!=s[i-L]) continue; 46 int l=1,r=L,last=i-L,pos=0; 47 //二分查找lcp和lcs 48 while(l<=r){ 49 int mid=l+r>>1; 50 if(gethash(last-mid+1,last+1)==gethash(i-mid+1,i+1)) pos=mid,l=mid+1; 51 else r=mid-1; 52 } 53 int head=i-pos+1; 54 l=1,r=L,pos=0; 55 while(l<=r){ 56 int mid=l+r>>1; 57 if(gethash(last,last+mid)==gethash(i,i+mid)) pos=mid,l=mid+1; 58 else r=mid-1; 59 } 60 int tail=i+pos-1; 61 head=max(head+L-1,i);//防止越过两块 62 tail=min(tail,i+L-1);//防止跑到后面的块 63 if(head<=tail){ 64 ++u[head-2*L+1],--u[tail+1-2*L+1]; 65 ++v[head],--v[tail+1]; 66 //为了差分 67 //因为head-2*L+1到tail-2*L+1开头的AA串增加的 68 //以他们的答案都可以++ 69 //然后以head到tail结尾的AA串也++ 70 } 71 } 72 } 73 ans=0; 74 for(int i=1;i<=n;++i) u[i]+=u[i-1],v[i]+=v[i-1]; 75 for(int i=1;i<n;++i) ans+=v[i]*u[i+1]; 76 print(ans); 77 } 78 Ot(); 79 return 0; 80 }
深深地明白自己的弱小