bzoj4556TJOI2016&HEOI2016字符串
Posted AaronPolaris
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了bzoj4556TJOI2016&HEOI2016字符串相关的知识,希望对你有一定的参考价值。
4556: [Tjoi2016&Heoi2016]字符串
Time Limit: 20 Sec Memory Limit: 128 MBSubmit: 195 Solved: 103
[Submit][Status][Discuss]
Description
佳媛姐姐过生日的时候,她的小伙伴从某东上买了一个生日礼物。生日礼物放在一个神奇的箱子中。箱子外边写了
一个长为n的字符串s,和m个问题。佳媛姐姐必须正确回答这m个问题,才能打开箱子拿到礼物,升职加薪,出任CE
O,嫁给高富帅,走上人生巅峰。每个问题均有a,b,c,d四个参数,问你子串s[a..b]的所有子串和s[c..d]的最长公
共前缀的长度的最大值是多少?佳媛姐姐并不擅长做这样的问题,所以她向你求助,你该如何帮助她呢?
Input
输入的第一行有两个正整数n,m,分别表示字符串的长度和询问的个数。接下来一行是一个长为n的字符串。接下来
m行,每行有4个数a,b,c,d,表示询问s[a..b]的所有子串和s[c..d]的最长公共前缀的最大值。1<=n,m<=100,000,
字符串中仅有小写英文字母,a<=b,c<=d,1<=a,b,c,d<=n
Output
对于每一次询问,输出答案。
Sample Input
5 5
aaaaa
1 1 1 5
1 5 1 1
2 3 2 3
2 4 2 3
2 3 2 4
aaaaa
1 1 1 5
1 5 1 1
2 3 2 3
2 4 2 3
2 3 2 4
Sample Output
1
1
2
2
2
1
2
2
2
方法一:后缀自动机+二分答案+倍增+可持久化线段树
后缀自动机的性质是,所有接收状态都表示一个后缀。但题目要求最长公共前缀,所以要将字符串翻转。(字符串翻转的小技巧要掌握...)
于是问题转化为:给定两个子串a和b,求的a所有子串和b的最长公共后缀的最大值。
然后二分答案,问题转化为判断mid是否合法。
判断方法:每个点用一个权值线段树记录right集合。在parent树上倍增,找到mx值第一个包含mid的地方,然后用该点的线段树判断。
这里又遇到另一个问题,线段树的合并。可以证明线段树的合并复杂度为O(n*logn)。
最终复杂度为O(n*log^2n)。
#include<iostream> #include<cmath> #include<cstdio> #include<cstdlib> #include<cstring> #include<algorithm> #define F(i,j,n) for(int i=j;i<=n;i++) #define D(i,j,n) for(int i=j;i>=n;i--) #define ll long long #define N 200005 #define M 4000005 using namespace std; int n,m; int pos[N]; int last=1,cnt=1,a[N][26],fa[N][20],mx[N],c[N],q[N]; int tot,rt[N],ls[M],rs[M]; char s[N]; inline int read() { int x=0,f=1;char ch=getchar(); while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();} while (ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } void insert(int &k,int l,int r,int x) { k=++tot; if (l==r) return; int mid=(l+r)>>1; if (x<=mid) insert(ls[k],l,mid,x); else insert(rs[k],mid+1,r,x); } int merge(int x,int y)//线段树的合并 { if (!x||!y) return x+y; int z=++tot; ls[z]=merge(ls[x],ls[y]);rs[z]=merge(rs[x],rs[y]); return z; } bool find(int k,int l,int r,int L,int R) { if (!k) return 0; if (l==L&&r==R) return 1; int mid=(l+r)>>1; if (R<=mid) return find(ls[k],l,mid,L,R); else if (L>mid) return find(rs[k],mid+1,r,L,R); else return find(ls[k],l,mid,L,mid)||find(rs[k],mid+1,r,mid+1,R); } void add(int x,int id) { int p=last,np=++cnt;last=np; mx[np]=mx[p]+1;pos[id]=np;insert(rt[np],1,n,id); while (p&&!a[p][x]) a[p][x]=np,p=fa[p][0]; if (!p) fa[np][0]=1; else { int q=a[p][x]; if (mx[q]==mx[p]+1) fa[np][0]=q; else { int nq=++cnt;mx[nq]=mx[p]+1; memcpy(a[nq],a[q],sizeof(a[q])); fa[nq][0]=fa[q][0];fa[np][0]=fa[q][0]=nq; while (a[p][x]==q) a[p][x]=nq,p=fa[p][0]; } } } void calc() { F(i,1,cnt) c[mx[i]]++; F(i,1,cnt) c[i]+=c[i-1]; F(i,1,cnt) q[c[mx[i]]--]=i; D(i,cnt,1) { int x=q[i],f=fa[x][0]; rt[f]=merge(rt[f],rt[x]); } F(j,1,18) F(i,1,cnt) fa[i][j]=fa[fa[i][j-1]][j-1]; } bool check(int mid,int x,int l,int r) { D(i,18,0) if (mx[fa[x][i]]>=mid) x=fa[x][i]; return find(rt[x],1,n,l,r); } int main() { n=read();m=read(); scanf("%s",s+1);reverse(s+1,s+n+1); F(i,1,n) add(s[i]-'a',i); calc(); F(i,1,m) { int a=n-read()+1,b=n-read()+1,c=n-read()+1,d=n-read()+1; swap(a,b);swap(c,d); int l=1,r=min(d-c+1,b-a+1),mid,ans=0; while (l<=r) { mid=(l+r)>>1; if (check(mid,pos[d],a+mid-1,b)) ans=mid,l=mid+1; else r=mid-1; } printf("%d\n",ans); } return 0; }
方法二:后缀数组
这个方法感觉是暴力,但是跑得飞快...
每次在后缀数组上暴力向前和向后找,当height[i]≤ans时停止搜索,相当于一个小的剪枝。
#include<iostream> #include<cmath> #include<cstdio> #include<cstdlib> #include<cstring> #include<algorithm> #define F(i,j,n) for(int i=j;i<=n;i++) #define D(i,j,n) for(int i=j;i>=n;i--) #define ll long long #define N 200005 #define inf 1000000000 using namespace std; int n,m; int x[N],y[N],c[N],sa[N],rnk[N],h[N]; char s[N]; inline int read() { int x=0,f=1;char ch=getchar(); while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();} while (ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } void build_sa() { int num=26,t; F(i,1,n) c[x[i]]++; F(i,2,num) c[i]+=c[i-1]; D(i,n,1) sa[c[x[i]]--]=i; for(int k=1;k<=n;k<<=1) { t=0; F(i,n-k+1,n) y[++t]=i; F(i,1,n) if (sa[i]>k) y[++t]=sa[i]-k; memset(c,0,sizeof(c)); F(i,1,n) c[x[i]]++; F(i,2,num) c[i]+=c[i-1]; D(i,n,1) sa[c[x[y[i]]]--]=y[i]; swap(x,y); t=1;x[sa[1]]=1; F(i,2,n) x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?t:++t; if (t>=n) break; num=t; } } void get_h() { int tmp=0,x; F(i,1,n) rnk[sa[i]]=i; F(i,1,n) { x=sa[rnk[i]-1]; if (tmp) tmp--; while (s[x+tmp]==s[i+tmp]) tmp++; h[rnk[i]]=tmp; } } int main() { n=read();m=read(); scanf("%s",s+1); F(i,1,n) x[i]=s[i]-'a'+1; build_sa(); get_h(); F(i,1,m) { int a=read(),b=read(),c=read(),d=read(); int pos=rnk[c],ans=0,mn=inf; if (a<=c&&c<=b) ans=min(d-c+1,b-c+1); D(i,pos,2) { if (h[i]<=ans) break; mn=min(mn,h[i]); if (sa[i-1]>=a&&sa[i-1]<=b) ans=max(ans,min(mn,min(d-c+1,b-sa[i-1]+1))); } mn=inf; F(i,pos+1,n) { if (h[i]<=ans) break; mn=min(mn,h[i]); if (sa[i]>=a&&sa[i]<=b) ans=max(ans,min(mn,min(d-c+1,b-sa[i]+1))); } printf("%d\n",ans); } return 0; }
以上是关于bzoj4556TJOI2016&HEOI2016字符串的主要内容,如果未能解决你的问题,请参考以下文章
Bzoj4556: [Tjoi2016&Heoi2016]字符串 后缀数组
[BZOJ4556][Tjoi2016&Heoi2016]字符串 后缀数组+主席树
[BZOJ4556][TJOI2016&&HEOI2016]字符串(二分答案+后缀数组+RMQ+主席树)
bzoj 4556: [Tjoi2016&Heoi2016]字符串