给你一个长度为 $n$ 的01串,$m$ 次询问,每次询问给出 $l$ 、$r$ ,求从 $[l,r]$ 中选出两个不同的前缀的最长公共后缀长度的最大值。
$n,m\le 10^5$
题解
后缀自动机+STL-set+启发式合并+离线+扫描线+树状数组
两个前缀的最长公共后缀,在正串后缀自动机上体现为pre树上两点LCA的深度。
考虑统计pre树上一个点的贡献:对于两个前缀 $x$ 、$y$ ,它能够影响的询问左端点小于等于 $x$ ,右端点大于等于 $y$ 。因此影响最大化的前缀对就是排序后相邻的两个,每次只需要考虑这些前缀对。
那么我们考虑两个子树合并的过程,使用STL-set维护前驱后继成为贡献。这个过程可以启发式合并,把小的合并到大的中。
剩下的就是对于询问 $[l,r]$ ,询问前缀对中第一个 $\ge l$ ,第二个 $\le r$ 的最大值。离线+扫描线+树状数组维护前缀最小值即可。
时间复杂度瓶颈在于启发式合并STL-set,为 $O(n\log^2n)$ 。
#include <set>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 200010
using namespace std;
struct data
{
int x , y , z;
data() {}
data(int a , int b , int c) {x = a , y = b , z = c;}
bool operator<(const data &a)const {return x < a.x;}
}a[N * 20] , q[N];
set<int> s[N];
int pre[N] , c[N][2] , dis[N] , val[N] , last = 1 , tot = 1 , head[N] , to[N] , next[N] , cnt , bl[N] , ta , f[N] , n , ans[N];
char str[N];
void insert(int k , int ch)
{
int p = last , np = last = ++tot;
dis[np] = dis[p] + 1 , s[np].insert(k);
while(p && !c[p][ch]) c[p][ch] = np , p = pre[p];
if(!p) pre[np] = 1;
else
{
int q = c[p][ch];
if(dis[q] == dis[p] + 1) pre[np] = q;
else
{
int nq = ++tot;
memcpy(c[nq] , c[q] , sizeof(c[q]));
dis[nq] = dis[p] + 1 , pre[nq] = pre[q] , pre[np] = pre[q] = nq;
while(p && c[p][ch] == q) c[p][ch] = nq , p = pre[p];
}
}
}
inline void add(int x , int y)
{
to[++cnt] = y , next[cnt] = head[x] , head[x] = cnt;
}
void dfs(int x)
{
int i;
set<int>::iterator j , k;
for(i = head[x] ; i ; i = next[i])
{
dfs(to[i]);
if(s[bl[x]].size() > s[bl[to[i]]].size())
{
for(j = s[bl[to[i]]].begin() ; j != s[bl[to[i]]].end() ; j ++ )
{
k = s[bl[x]].upper_bound(*j);
if(k != s[bl[x]].end()) a[++ta] = data(*j , *k , dis[x]);
if(k != s[bl[x]].begin()) a[++ta] = data(*--k , *j , dis[x]);
s[bl[x]].insert(*j);
}
}
else
{
for(j = s[bl[x]].begin() ; j != s[bl[x]].end() ; j ++ )
{
k = s[bl[to[i]]].upper_bound(*j);
if(k != s[bl[to[i]]].end()) a[++ta] = data(*j , *k , dis[x]);
if(k != s[bl[to[i]]].begin()) a[++ta] = data(*--k , *j , dis[x]);
s[bl[to[i]]].insert(*j);
}
bl[x] = bl[to[i]];
}
}
}
inline void fix(int x , int a)
{
int i;
for(i = x ; i <= n ; i += i & -i) f[i] = max(f[i] , a);
}
inline int query(int x)
{
int i , ans = 0;
for(i = x ; i ; i -= i & -i) ans = max(ans , f[i]);
return ans;
}
int main()
{
int m , i , p;
scanf("%d%d%s" , &n , &m , str + 1);
for(i = 1 ; i <= n ; i ++ ) insert(i , str[i] - '0');
for(i = 2 ; i <= tot ; i ++ ) add(pre[i] , i);
for(i = 1 ; i <= tot ; i ++ ) bl[i] = i;
dfs(1);
for(i = 1 ; i <= m ; i ++ ) scanf("%d%d" , &q[i].x , &q[i].y) , q[i].z = i;
sort(a + 1 , a + ta + 1) , sort(q + 1 , q + m + 1);
for(p = ta , i = m ; i ; i -- )
{
while(p && a[p].x >= q[i].x) fix(a[p].y , a[p].z) , p -- ;
ans[q[i].z] = query(q[i].y);
}
for(i = 1 ; i <= m ; i ++ ) printf("%d\n" , ans[i]);
return 0;
}