BZOJ4556: [Tjoi2016&Heoi2016]字符串
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
题解Here!
首先看到最长公共前缀就知道,又是一道后缀数组。。。
设我们的答案为$mid$,那么我们会发现一个很有趣的事实:如果$mid$可行的话,那么任意一个比$mid$小的数也可行。
也就是说,问题满足可二分性,那么我们可以二分答案,将原问题转化为一个判定性问题:$mid$这个答案行不行?
那么我们发现,如果$mid$这个答案可以的话,就会存在一个后缀$S$,
- 它的开头在$[a,b-mid+1]$当中。
- $lcp(S,c)>=mid$。
再次转化一步,就是询问满足以上两个条件的后缀$S$的个数。
经典的二元限制统计问题。。。
我们的思路很简单,摁死一个再去管下一个,发现一件有趣的事实:
如果把这些后缀排好序,那么$lcp$符合要求的一定是一段连续的区间。
为什么?
因为我们发现排好序以后,$lcp$这个函数是单峰的,并且峰值在自己这里。
那么我们似乎可以二分左端点和右端点,需要$O(1)$求出区间最小值,直接套上$ST$表。
那么最后我们发现现在两个限制都是区间型的了,而且是静态区间,没有修改,所以可以用主席树查询一发。
复杂度是$O(nlog_2^2n)$。
然后就光荣地在$BZOJ$上被卡常了。。。
$UPDATE$:经过某些玄学优化,终于在$8s$内过了。。。
附代码:
#include<iostream> #include<algorithm> #include<cstdio> #include<cmath> #define MAXN 100010 using namespace std; int n,m; int root[MAXN]; char str[MAXN]; int top,sa[MAXN],rk[MAXN],tax[MAXN],tp[MAXN],height[MAXN],f[MAXN][20],Log[MAXN]; inline int read(){ int date=0,w=1;char c=0; while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();} while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();} return date*w; } namespace CT{ int size=0; struct Chairman_Tree{ int l,r,sum; }a[MAXN*20]; inline void buildtree(){ root[0]=a[0].l=a[0].r=a[0].sum=0; } void insert(int k,int v,int l,int r,int &rt){ a[++size]=a[rt];rt=size; a[rt].sum+=v; if(l==r)return; int mid=l+r>>1; if(k<=mid)insert(k,v,l,mid,a[rt].l); else insert(k,v,mid+1,r,a[rt].r); } int query(int l,int r,int lside,int rside,int i,int j){ if(a[i].sum==a[j].sum)return 0; if(l<=lside&&rside<=r)return (a[j].sum-a[i].sum); int mid=lside+rside>>1,ans=0; if(l<=mid)ans+=query(l,r,lside,mid,a[i].l,a[j].l); if(mid<r)ans+=query(l,r,mid+1,rside,a[i].r,a[j].r); return ans; } } void radixsort(){ for(int i=0;i<=top;i++)tax[i]=0; for(int i=1;i<=n;i++)tax[rk[i]]++; for(int i=1;i<=top;i++)tax[i]+=tax[i-1]; for(int i=n;i>=1;i--)sa[tax[rk[tp[i]]]--]=tp[i]; } void suffixsort(){ top=30; for(int i=1;i<=n;i++){ rk[i]=str[i]-'a'+1; tp[i]=i; } radixsort(); for(int w=1,p=0;p<n;top=p,w<<=1){ p=0; for(int i=1;i<=w;i++)tp[++p]=n-w+i; for(int i=1;i<=n;i++)if(sa[i]>w)tp[++p]=sa[i]-w; radixsort(); swap(tp,rk); rk[sa[1]]=p=1; for(int i=2;i<=n;i++) rk[sa[i]]=(tp[sa[i-1]]==tp[sa[i]]&&tp[sa[i-1]+w]==tp[sa[i]+w])?p:++p; } } void getheight(){ for(int i=1,j,k=0;i<=n;i++){ if(k)k--; j=sa[rk[i]-1]; while(str[i+k]==str[j+k])k++; height[rk[i]]=k; } } void step(){ Log[0]=0; for(int i=1;i<=n;i++){ f[i][0]=height[i]; Log[i]=log2(i); } for(int i=1;i<=Log[n];i++) for(int j=1;j+(1<<(i-1))<=n;j++) f[j][i]=min(f[j][i-1],f[j+(1<<(i-1))][i-1]); } inline int query(int l,int r){ l++;r++; int k=Log[r-l]; return min(f[l][k],f[r-(1<<k)][k]); } bool check(int x,int l1,int r1,int l2,int r2){ int Left,Right; int l=1,r=rk[l2]; while(l<r){ int mid=l+r>>1; if(query(mid,rk[l2])<x)l=mid+1; else r=mid; } Left=r; l=rk[l2];r=n; while(l<r){ int mid=l+r+1>>1; if(query(rk[l2],mid)<x)r=mid-1; else l=mid; } Right=r; if(CT::query(l1,r1-x+1,1,n,root[Left-1],root[Right]))return true; return false; } inline int solve(int l1,int r1,int l2,int r2){ int l=0,r=min(r1-l1+1,r2-l2+1); while(l<r){ int mid=l+r+1>>1; if(check(mid,l1,r1,l2,r2))l=mid; else r=mid-1; } return r; } void work(){ int l1,l2,r1,r2; while(m--){ l1=read();r1=read();l2=read();r2=read(); printf("%d ",solve(l1,r1,l2,r2)); } } void init(){ n=read();m=read(); scanf("%s",str+1); suffixsort(); getheight(); step(); CT::buildtree(); for(int i=1;i<=n;i++){ root[i]=root[i-1]; CT::insert(sa[i],1,1,n,root[i]); } } int main(){ init(); work(); return 0; }
据说这题可以用$SAM$搞事?
暂且留个坑吧,等学完$SAM$再来填坑。。。