[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 }

 

 
 

转载于:https://www.cnblogs.com/bztMinamoto/p/9470029.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值