[字符串题单][TJOI / HEOI2016]字符串
壹、题目描述
贰、蒟蒻思考
询问问的其实就是最长公共子串的长度,这个没什么问题,如果只有一个询问,我们可以考虑暴力检出 \(\tt SAM\) 然后进行匹配,但是询问目前有 \(m\le 100000\) 个,如果将询问暴力挂在 \(\text{parent tree}\) 上,或许空间都不够用,这个怎么办?
搁浅了......
卧槽发现题读错了,我们要求的是一个区间与一个串的 \(\rm LCP\),但是还是没有思路......
叁、题解
这个题是真的 \(\rm ex\) 啊......我们如果直接使用 \(\tt SA\),贪心地求 \(\text{LCP}\) 最大的,这样可能会出问题,因为我们的确会找到最大的 \(\rm LCP\),但是它可能会出界,这样我们只能取其在区间中的部分,这一部分可能比较短,这样的问题怎么解决?
考虑先二分一个答案,得到一个 \(L\),现在我们要求的其实就是是否存在一个 \(i\in [a,b-L+1]\) 使得 \(\text{LCP}(s[i:],s[c:])\ge L\),对于这个问题,我们可有考虑建出 \(\tt SA\),对于这个 \(\tt SA\),我们求出后缀 \(s[c:]\) 最多向左右两边走多少步使得 \(\text{heit}\ge L\),得到这个区间 \([l,r]\),我们要求的就是在后缀排序的 \([l,r]\) 中,有没有一个后缀属于区间 \([a,b-L+1]\) 中,这是一个二维数点问题,我们可以考虑使用主席树进行维护。
肆、代码
\(\color{red}{\text{talk is segment tree, show you the code.}}\)
#include<cstdio>
#include<algorithm>
using namespace std;
#define Endl putchar('\n')
typedef long long ll;
template<class T>inline T readin(T x){
x=0; int f=0; char c;
while((c=getchar())<'0' || '9'<c) if(c=='-') f=1;
for(x=(c^48); '0'<=(c=getchar()) && c<='9'; x=(x<<1)+(x<<3)+(c^48));
return f? -x: x;
}
const int maxn=1e5;
const int logmaxn=34;
const int inf=(1<<30)-1;
int n, m;
char s[maxn+5];
inline void input(){
n=readin(1), m=readin(1);
scanf("%s", s+1);
}
namespace segment_tre{
int rt[maxn+5], cnt[maxn*50+5];
int ls[maxn*50+5], rs[maxn*50+5];
int ncnt=0;
#define mid ((l+r)>>1)
#define _lq ls[i], l, mid
#define _rq rs[i], mid+1, r
inline int copy(const int i){
++ncnt;
ls[ncnt]=ls[i], rs[ncnt]=rs[i];
cnt[ncnt]=cnt[i];
return ncnt;
}
void insert(const int pre, const int p, int& i, const int l, const int r){
i=copy(pre); ++cnt[i];
if(l==r) return;
if(p<=mid) return insert(ls[pre], p, ls[i], l, mid);
else return insert(rs[pre], p, rs[i], mid+1, r);
}
// 询问区间 [L, R] 中有没有点
int query(const int r1, const int r2, const int L, const int R, const int l, const int r){
if(cnt[r2]-cnt[r1]==0) return 0;
if(L<=l && r<=R) return 1;
int ret=0;
if(L<=mid) ret|=query(ls[r1], ls[r2], L, R, l, mid);
if(mid<R) ret|=query(rs[r1], rs[r2], L, R, mid+1, r);
return ret;
}
inline void insert(const int x, const int val){
insert(rt[x-1], val, rt[x], 1, n);
}
inline int query(const int a, const int b, const int l, const int r){
// printf("seg::query :> a == %d, b == %d, l == %d, r == %d\n", a, b, l, r);
return query(rt[a-1], rt[b], l, r, 1, n);
}
#undef ls
#undef rs
#undef mid
#undef _lq
#undef _rq
}
namespace SA{
int sa[maxn+5], c[maxn+5];
int x[maxn*2+5], y[maxn*2+5];
int ccnt;
inline void getsa(){
ccnt='z';
for(int i=1; i<=n; ++i) ++c[x[i]=s[i]];
for(int i=1; i<=ccnt; ++i) c[i]+=c[i-1];
for(int i=n; i>=1; --i) sa[c[x[i]]--]=i;
for(int k=1; k<=n; k<<=1){
int sz=0;
for(int i=n-k+1; i<=n; ++i) y[++sz]=i;
for(int i=1; i<=n; ++i) if(sa[i]>k)
y[++sz]=sa[i]-k;
for(int i=0; i<=ccnt; ++i) c[i]=0;
for(int i=1; i<=n; ++i) ++c[x[i]];
for(int i=1; i<=ccnt; ++i) c[i]+=c[i-1];
for(int i=n; i>=1; --i)
sa[c[x[y[i]]]--]=y[i], y[i]=0;
swap(x, y); // 注意 y 的含义发生改变
x[sa[1]]=1; int level=1;
for(int i=2; i<=n; ++i)
x[sa[i]]=(y[sa[i]]==y[sa[i-1]] && y[sa[i]+k]==y[sa[i-1]+k])? level: ++level;
if(level==n) break;
ccnt=level;
}
}
int heit[maxn+5], rk[maxn+5];
inline void getheit(){
int h=0;
for(int i=1; i<=n; ++i) rk[sa[i]]=i;
for(int i=1; i<=n; ++i){
if(rk[i]==1)continue;
if(h) --h;
int j=sa[rk[i]-1];
while(i+h<=n && j+h<=n && s[i+h]==s[j+h]) ++h;
heit[rk[i]]=h;
}
}
int st[maxn+5][logmaxn+5];
int lgg[maxn+5];
inline void buildst(){
lgg[0]=-1;
for(int i=1; i<=n; ++i) lgg[i]=lgg[i>>1]+1;
for(int i=1; i<=n; ++i) st[i][0]=heit[i];
for(int j=1; j<=logmaxn; ++j){
for(int i=1; i<=n-(1<<(j-1)); ++i)
st[i][j]=min(st[i][j-1], st[i+(1<<(j-1))][j-1]);
}
}
inline int query(const int l,const int r){
// printf("st::query :> l == %d, r == %d\n", l, r);
if(l>r) return inf;
int x=lgg[r-l+1];
// printf("return %d\n", min(st[l][x], st[r-(1<<x)+1][x]));
return min(st[l][x], st[r-(1<<x)+1][x]);
}
inline void check(){
puts("<---------------- SA checker ---------------->");
printf("this is sa:\n");
for(int i=1; i<=n; ++i) printf("%d ", sa[i]);
Endl;
printf("this is rk:\n");
for(int i=1; i<=n; ++i) printf("%d ", rk[i]);
Endl;
printf("this is heit:\n");
for(int i=1; i<=n; ++i) printf("%d ", heit[i]);
Endl;
puts("<---------------- SA checker END ---------------->");
}
}
inline void buildtre(){
for(int i=1; i<=n; ++i)
segment_tre::insert(i, SA::rk[i]);
}
int a, b, c, d;
inline int exist(const int L){
// printf("exist :> L == %d\n", L);
int x=SA::rk[c], up=x, down=x;
// printf("x == %d\n", x);
int l, r, mid;
l=1, r=x;
while(l<=r){
mid=(l+r)>>1;
if(SA::query(mid+1, x)>=L) down=mid, r=mid-1;
else l=mid+1;
}
l=x, r=n;
while(l<=r){
mid=(l+r)>>1;
if(SA::query(x+1, mid)>=L) up=mid, l=mid+1;
else r=mid-1;
}
// printf("When L == %d, [%d, %d]\n", L, down, up);
return segment_tre::query(a, b-L+1, down, up)>0;
}
inline void getquery(){
int l, r, mid, ans;
while(m--){
a=readin(1), b=readin(1), c=readin(1), d=readin(1);
l=1, r=min(b-a+1, d-c+1), ans=0;
while(l<=r){
mid=(l+r)>>1;
// printf("the first bisearch:> l == %d, r == %d, mid == %d\n", l, r, mid);
if(exist(mid)) ans=mid, l=mid+1;
else r=mid-1;
}
printf("%d\n", ans);
}
}
signed main(){
input();
SA::getsa();
SA::getheit();
// SA::check();
SA::buildst();
buildtre();
getquery();
return 0;
}
伍、用到の小 \(\tt trick\)
首先是读题......
其次,由于一些限制问题,我们可以使用二分的初始区间来刻画限制。
最后,二维数点问题我们可以考虑使用主席树进行维护,这个比较经典了吧?