BZOJ4556: [Tjoi2016&Heoi2016]字符串
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(n\log_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\n",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$再来填坑。。。

浙公网安备 33010602011771号