P4094 [HEOI2016/TJOI2016]字符串(二分+多种数据结构)

P4094 [HEOI2016/TJOI2016]字符串(二分+多种数据结构)

在洛谷写的第一道黑题留念。第一次一遍默写对了那么多数据结构的板子,嘻嘻。
问题是这样的:每次询问max{字符串S的某一子串A的所有子串和另一子串B的lcp}

如果暴力枚举A的子串那一定是会超时的

这里注意到如果x是满足条件的答案,那么比x小的数也能够满足条件,因此答案满足单调性,可以将问题转换为判断一个值是否能满足条件

首先x不能超过min{len(A的子串),len(B)},由于B是定的,我们可以用rk数组找到B在所有后缀中的位置,这里又注意到其他后缀与B的lcp也符合单调性质,B往上往下的后缀与B的lcp递减,此时可以从B的位置向两边二分出满足条件的最小lcp的位置的L和R,此时只要判断这个区间内是否含有在子串A[l,r]的子串[l,r-x+1]中的后缀,需要快速做出这个判断,可以对sa数组建立可持久化权值线段树,每次二分x时查询L到R版本之间是否存在满足条件的后缀。

复杂度是倍增算sa的nlogn+询问数m×第一个二分的logn×第二个二分+主席树的logn也就是(O(nlogn+mlog^2n))

const int N = 2e5+10;
int n, m, q;

struct seg{
  int l, r, val;
}node[N*40];
int root[N];
char s[N];
int sa[N], x[N], y[N], c[N], rk[N], height[N];

int LOG[N] = {0, 0, 1};
int st[N][22];

void get_sa() {
  rep(i,1,n) c[x[i]=s[i]]++;
  rep(i,2,m) c[i] += c[i-1];
  per(i,n,1) sa[c[x[i]]--] = i;
  for (int k = 1; k <= n; k <<= 1) {
    int num = 0;
    rep(i,n-k+1,n) y[++num] = i;
    rep(i,1,n) if (sa[i] > k) y[++num] = sa[i] - k;
    rep(i,1,m) c[i] = 0;
    rep(i,1,n) c[x[i]]++;
    rep(i,2,m) c[i] += c[i-1];
    per(i,n,1) sa[c[x[y[i]]]--] = y[i];
    swap(x,y);
    x[sa[1]] = 1, num = 1;
    rep(i,2,n) x[sa[i]] = (y[sa[i]] == y[sa[i-1]] && y[sa[i]+k] == y[sa[i-1]+k]) ? num : ++num;
    if (num == n) break;
    m = num;
  }
}

void get_height() {
  rep(i,1,n) rk[sa[i]] = i;
  for (int i = 1, k = 0; i <= n; i++) {
    if (rk[i] == 1) continue;
    if (k) --k;
    int j = sa[rk[i]-1];
    while (i + k <= n && j + k <= n && s[i+k] == s[j+k]) ++k;
    height[rk[i]] = k;
  }
}

void get_st() {
  rep(i,1,n) st[i][0] = height[i];
  rep(j,1,LOG[n]) {
    for (int i = 1; i + (1ll<<j) - 1 <= n; i++) {
      st[i][j] = min(st[i][j-1], st[i+(1ll<<(j-1))][j-1]);
    }
  }
}

int rmq(int l, int r) {
  int len = r-l+1, x = LOG[len];
  return min(st[l][x], st[r-(1ll<<x)+1][x]);
}

int cnt;

void ins(int l, int r, int pre, int &now, int p) {
  node[now=++cnt] = node[pre];
  node[now].val++;
  if (l == r) return;
  int mid = l + r >> 1;
  if (p <= mid) ins(l, mid, node[pre].l, node[now].l, p);
  else ins(mid+1, r, node[pre].r, node[now].r, p);
}

int query(int l, int r, int ll, int rr, int L, int R) {
  if (ll <= l && rr >= r) return node[R].val - node[L].val;
  int mid = l + r >> 1;
  int res = 0;
  if (ll <= mid) res += query(l, mid, ll, rr, node[L].l, node[R].l);
  if (rr > mid) res += query(mid+1, r, ll, rr, node[L].r, node[R].r);
  return res;
}

bool check(int x, int a, int b, int c) {
  int MID = rk[c], l, r, L = MID, R = MID;
  l = 1, r = MID;
  while (l <= r) {
    int mid = l + r >> 1;
    if (rmq(mid+1,MID) >= x) L = mid, r = mid - 1;
    else l = mid + 1; 
  }
  l = MID+1, r = n;
  while (l <= r) {
    int mid = l + r >> 1;
    if (rmq(MID+1, mid) >= x) R = mid, l = mid + 1;
    else r = mid - 1;
  }
  return query(1, n, a, b-x+1, root[L-1], root[R]) > 0;
}

int solve(int a, int b, int c, int d) {
  int l = 0, r = min(b-a+1, d-c+1);
  while (l < r) {
    int mid = l + r + 1 >> 1;
    if (check(mid, a, b, c)) l = mid;
    else r = mid - 1;
  }
  return l;
}

void run() {
  n = rd(), q = rd(), m = 122;
  scanf("%s",s+1);
  get_sa();
  get_height();
  get_st();
  rep(i,1,n) ins(1,n,root[i-1],root[i],sa[i]);
  while (q--) {
    int a = rd(), b = rd(), c = rd(), d = rd();
    pnt(solve(a,b,c,d));
  }
}

signed main(){
  rep(i,3,N-1) LOG[i] = LOG[i>>1] + 1;
//  for (tt = rd(); tt--; )
  run();
  return 0;
}
原文地址:https://www.cnblogs.com/hznudreamer/p/14067689.html