【BZOJ4650】【NOI2016】优秀的拆分(后缀数组)
2018-01-26 10:03
417 查看
【BZOJ4650】【NOI2016】优秀的拆分(后缀数组)
题面
BZOJUoj
题解
如果我们知道以某个位置为开始/结尾的\(AA\)串的个数那就直接做一下乘法就好
这个怎么求?
枚举一个位置
枚举串的长度
直接暴力算就好啦
至于是否可行,用\(SA\)求\(lcp\)就好啦
这样就是\(95\)分
NOI这么好拿部分分的???
#include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include<cmath> #include<algorithm> #include<set> #include<map> #include<vector> #include<queue> using namespace std; #define ll long long #define MAX 35000 int x[MAX],y[MAX],t[MAX]; int SA[MAX],height[MAX],rk[MAX]; int lg[MAX],n,p[20][MAX],a[MAX]; char s[MAX]; int g[MAX],f[MAX],T; bool cmp(int i,int j,int k){return y[i]==y[j]&&y[i+k]==y[j+k];} void init() { memset(SA,0,sizeof(SA)); memset(height,0,sizeof(height)); memset(rk,0,sizeof(rk)); memset(x,0,sizeof(x)); memset(y,0,sizeof(y)); memset(t,0,sizeof(t)); memset(a,0,sizeof(a)); } void GetSA() { int m=50; for(int i=1;i<=n;++i)t[x[i]=a[i]]++; for(int i=1;i<=m;++i)t[i]+=t[i-1]; for(int i=n;i>=1;--i)SA[t[x[i]]--]=i; for(int k=1;k<=n;k<<=1) { int p=0; for(int i=n-k+1;i<=n;++i)y[++p]=i; for(int i=1;i<=n;++i)if(SA[i]>k)y[++p]=SA[i]-k; for(int i=0;i<=m;++i)t[i]=0; for(int i=1;i<=n;++i)t[x[y[i]]]++; for(int i=1;i<=m;++i)t[i]+=t[i-1]; for(int i=n;i>=1;--i)SA[t[x[y[i]]]--]=y[i]; swap(x,y); x[SA[1]]=p=1; for(int i=2;i<=n;++i) x[SA[i]]=cmp(SA[i],SA[i-1],k)?p:++p; if(p>=n)break; m=p; } for(int i=1;i<=n;++i)rk[SA[i]]=i; for(int i=1,j=0;i<=n;++i) { if(j)--j; while(a[i+j]==a[SA[rk[i]-1]+j])++j; height[rk[i]]=j; } } void Pre() { memset(p,63,sizeof(p)); for(int i=1;i<=n;++i)p[0][i]=height[i]; for(int j=1;j<15;++j) for(int i=1;i<=n;++i) p[j][i]=min(p[j-1][i],p[j-1][i+(1<<(j-1))]); } int Query(int i,int j) { return min(p[lg[j-i+1]][i],p[lg[j-i+1]][j-(1<<lg[j-i+1])+1]); } int lcp(int i,int j) { int l=min(rk[i],rk[j])+1,r=max(rk[i],rk[j]); return Query(l,r); } int main() { for(int i=2;i<=30000;++i)lg[i]=lg[i>>1]+1; scanf("%d",&T); while(T--) { init(); scanf("%s",s+1); n=strlen(s+1); for(int i=1;i<=n;++i)a[i]=s[i]-96; GetSA();Pre(); for(int i=1;i<=n;++i) { g[i]=0; for(int l=1;l+l+i-1<=n;++l) if(lcp(i,i+l)>=l)g[i]++; } for(int i=2;i<=n;++i) { f[i]=0; for(int l=1;i-l-l+1>0;++l) if(lcp(i-l-l+1,i-l+1)>=l)f[i]++; } int ans=0; for(int i=1;i<n;++i) ans+=f[i]*g[i+1]; printf("%d\n",ans); } return 0; }
\(95\)分的暴力太显然了。。
原来\(NOI\)都是这样送分???
为什么NOIP 没有这么好的福利
想想怎么优化吧。。。
肯定不能枚举长度之后再暴力算每一个位置
那么,我们要考虑一个方法,
可以一次性算出连续的位置
想想我们怎么求\(AA\)这种形式??
计算\(lcp(i,i+len)>=len\)是否成立
但是,如果\(lcp(i,i+len)>=len\)
我们就会发现,有一段区间内都是有满足条件的子串
所以我们可以一起计算
现在仔细思考怎么算
因为每次是\(i\)和\(i+len\)
所以我们只要枚举位置是\(len\)的倍数的地方就好
旁边的地方我们要想办法算出来
第一个,是向后如果可以增加的话
\(lcp(i,i+len)>=L\)我就会获得向后的一段连续区间
如果只算向后,会忽略掉向前的一段
所以再算一下\(lcs(i,i+len)\)这段,这两边拼起来
如果满足条件,证明这一段区间都是可行的
这样就可以差分全部\(+1\)
如果重复的部分够多
这样算可能会影响到别的块里面
所以要强制只在自己这一段里面算
具体的实现看代码啦
#include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include<cmath> #include<algorithm> #include<set> #include<map> #include<vector> #include<queue> using namespace std; #define ll long long #define MAX 35000 int lg[MAX],n; char s[MAX]; int g[MAX],f[MAX],T; struct SA { int p[20][MAX],a[MAX]; int x[MAX],y[MAX],t[MAX]; int SA[MAX],height[MAX],rk[MAX]; bool cmp(int i,int j,int k){return y[i]==y[j]&&y[i+k]==y[j+k];} void init() { memset(SA,0,sizeof(SA)); memset(height,0,sizeof(height)); memset(rk,0,sizeof(rk)); memset(x,0,sizeof(x)); memset(y,0,sizeof(y)); memset(t,0,sizeof(t)); memset(a,0,sizeof(a)); } void GetSA() { int m=50; for(int i=1;i<=n;++i)t[x[i]=a[i]]++; for(int i=1;i<=m;++i)t[i]+=t[i-1]; for(int i=n;i>=1;--i)SA[t[x[i]]--]=i; for(int k=1;k<=n;k<<=1) { int p=0; for(int i=n-k+1;i<=n;++i)y[++p]=i; for(int i=1;i<=n;++i)if(SA[i]>k)y[++p]=SA[i]-k; for(int i=0;i<=m;++i)t[i]=0; for(int i=1;i<=n;++i)t[x[y[i]]]++; for(int i=1;i<=m;++i)t[i]+=t[i-1]; for(int i=n;i>=1;--i)SA[t[x[y[i]]]--]=y[i]; swap(x,y); x[SA[1]]=p=1; for(int i=2;i<=n;++i) x[SA[i]]=cmp(SA[i],SA[i-1],k)?p:++p; if(p>=n)break; m=p; } for(int i=1;i<=n;++i)rk[SA[i]]=i; for(int i=1,j=0;i<=n;++i) { if(j)--j; while(a[i+j]==a[SA[rk[i]-1]+j])++j; height[rk[i]]=j; } } void Pre() { memset(p,63,sizeof(p)); for(int i=1;i<=n;++i)p[0][i]=height[i]; for(int j=1;j<15;++j) for(int i=1;i<=n;++i) p[j][i]=min(p[j-1][i],p[j-1][i+(1<<(j-1))]); } int Query(int i,int j) { return min(p[lg[j-i+1]][i],p[lg[j-i+1]][j-(1<<lg[j-i+1])+1]); } int lcp(int i,int j) { int l=min(rk[i],rk[j])+1,r=max(rk[i],rk[j]); return Query(l,r); } }A,B; int main() { for(int i=2;i<=30000;++i)lg[i]=lg[i>>1]+1; scanf("%d",&T); while(T--) { A.init();B.init(); scanf("%s",s+1); n=strlen(s+1); for(int i=1;i<=n;++i)A.a[i]=s[i]-96; for(int i=1;i<=n;++i)B.a[n-i+1]=s[i]-96; A.GetSA();A.Pre();B.GetSA();B.Pre(); for(int i=1;i<=n;++i)g[i]=f[i]=0; for(int len=1;len<=n/2;++len) { for(int i=len,j=i+len;j<=n;i+=len,j+=len) { int x=min(A.lcp(i,j),len); int y=min(B.lcp(n-i+2,n-j+2),len-1); int t=x+y-len+1; if(x+y>=len) { g[i-y]++;g[i-y+t]--; f[j+x-t]++;f[j+x]--; } } } for(int i=1;i<=n;++i)g[i]+=g[i-1]; for(int i=1;i<=n;++i)f[i]+=f[i-1]; ll ans=0; for(int i=1;i<n;++i) ans+=1ll*f[i]*g[i+1]; printf("%lld\n",ans); } return 0; }
相关文章推荐
- BZOJ 4650 [Noi2016]优秀的拆分 ——后缀数组
- bzoj 4650: [Noi2016]优秀的拆分 后缀数组
- bzoj4650: [Noi2016]优秀的拆分 hash
- [BZOJ]4650 优秀的拆分(Noi2016)(哈希+二分)
- bzoj 4650: [Noi2016]优秀的拆分
- BZOJ 4650([Noi2016]优秀的拆分-SA)
- BZOJ4650:[NOI2016]优秀的拆分——题解
- bzoj4650: [Noi2016]优秀的拆分
- BZOJ 4650: [Noi2016]优秀的拆分 哈希+分块
- BZOJ4650 : [NOI2016]优秀的拆分
- BZOJ4650 : [Noi2016]优秀的拆分
- [BZOJ4650][NOI2016]优秀的拆分-后缀数组
- BZOJ4650 : [NOI2016]优秀的拆分
- [BZOJ]4650 优秀的拆分(Noi2016)
- [BZOJ]4650: [Noi2016]优秀的拆分
- [BZOJ4650][NOI2016]优秀的拆分 各数据点解法
- BZOJ4650/UOJ219 [Noi2016]优秀的拆分
- [后缀数组 枚举 字符串分段] BZOJ 4650 [Noi2016]优秀的拆分
- 【UOJ 219】【BZOJ 4650】【NOI 2016】优秀的拆分(后缀数组)
- BZOJ4650: [Noi2016]优秀的拆分