Solution -「洛谷 P6292」区间本质不同子串个数
\(\mathcal{Description}\)
Link.
给定长度为 \(n\),仅包含小写字符的字符串 \(s\),\(m\) 次询问,每次询问一个子串 \(s[l:r]\) 的本质不同子串数量。
\(n\le10^5\),\(m\le2\times10^5\)。
\(\mathcal{Solution}\)
有种常见的离线技巧:类似扫描线,从左至右枚举右端点 \(r\),维护 \([1..r,r]\) 的答案。为了让 \(s[1:r]\) 里的每个子串都尽量参与贡献,可以钦定某个子串 \(T\) 在其最后出现的位置贡献答案。设其最后出现位置的右端点为 \(p\),则它会使 \(l\in[1,p-|T|+1]\) 的询问 \([l,r]\) 的答案增加 \(1\)。我们只需要维护这一过程。
联系“本质不同子串”,容易想到使用 SAM。对于 \(s\) SAM 上的每个结点 \(u\),维护 \(p_u\) 表示其最后出现位置,那么右端点移动一次,设移动到 \(r'\),\(s[1:r']\) 在 SAM 上对应 \(u\),本次移动带来的影响便是 \(u\) 及其 fail 树上祖先们的 \(p\) 值全部变为 \(r'\),类似 LCT 的 access 操作。进一步,我们直接使用 LCT 维护这一过程,由于在 fail 树上,一条断开或链接上的树链本质上对应着一段长度连续的子串,再结合每个子串 \(T\) 对答案的影响形式,可以看出树链操作会使答案区间加上或减去一个公差为 \(1\) 的等比数列,差分后用线段树维护区间加、区间求和即可。
Access 均摊断边次数 \(\mathcal O(\log n)\),故有 \(\mathcal O(n\log n)\) 次区间修改,总复杂度为 \(\mathcal O((m+n\log n)\log n)\)。
\(\mathcal{Code}\)
代码真的非常好写 awa!
/* Clearink */
#include <cstdio>
#include <vector>
#include <cstring>
#define rep( i, l, r ) for ( int i = l, rep##i = r; i <= rep##i; ++i )
#define per( i, r, l ) for ( int i = r, per##i = l; i >= per##i; --i )
typedef long long LL;
typedef std::pair<int, int> PII;
#define fi first
#define se second
inline int rint() {
int x = 0, s = getchar();
for ( ; s < '0' || '9' < s; s = getchar() );
for ( ; '0' <= s && s <= '9'; s = getchar() ) x = x * 10 + ( s ^ '0' );
return x;
}
inline void wint( const LL x ) {
if ( 9 < x ) wint( x / 10 );
putchar( x % 10 ^ '0' );
}
const int MAXN = 1e5, MAXM = 2e5;
int n, m;
LL ans[MAXM + 5];
char s[MAXN + 5];
std::vector<PII> ask[MAXN + 5];
inline void chkmin( int& a, const int b ) { b < a && ( a = b ); }
struct SuffixAutomaton {
static const int MAXND = MAXN << 1;
int node, last,
ch[MAXND + 5][26], fail[MAXND + 5], mx[MAXND + 5], pos[MAXN + 5];
SuffixAutomaton(): node( 1 ), last( 1 ) {}
inline void extend( const int id, const int c ) {
int cur = ++node, p = last; mx[cur] = mx[p] + 1;
for ( ; p && !ch[p][c]; ch[p][c] = cur, p = fail[p] );
if ( !p ) fail[cur] = 1;
else {
int q = ch[p][c];
if ( mx[q] == mx[p] + 1 ) fail[cur] = q;
else {
int r = ++node; mx[r] = mx[p] + 1, fail[r] = fail[q];
rep ( i, 0, 25 ) ch[r][i] = ch[q][i];
for ( ; ch[p][c] == q; ch[p][c] = r, p = fail[p] );
fail[cur] = fail[q] = r;
}
}
pos[id] = last = cur;
}
} sam;
struct SegmentTree {
LL sum[MAXN << 2]; int tag[MAXN << 2];
inline void pushad( const int u, const int v, const int l, const int r ) {
sum[u] += ( r - l + 1ll ) * v;
tag[u] += v;
}
inline void pushdn( const int u, const int l, const int r ) {
if ( !tag[u] ) return ;
int mid = l + r >> 1;
pushad( u << 1, tag[u], l, mid );
pushad( u << 1 | 1, tag[u], mid + 1, r );
tag[u] = 0;
}
inline void pushup( const int u ) {
sum[u] = sum[u << 1] + sum[u << 1 | 1];
}
inline void add( const int u, const int l, const int r,
const int al, const int ar, const int v ) {
if ( al <= l && r <= ar ) return pushad( u, v, l, r );
int mid = l + r >> 1; pushdn( u, l, r );
if ( al <= mid ) add( u << 1, l, mid, al, ar, v );
if ( mid < ar ) add( u << 1 | 1, mid + 1, r, al, ar, v );
pushup( u );
}
inline LL query( const int u, const int l, const int r,
const int ql, const int qr ) {
if ( ql <= l && r <= qr ) return sum[u];
int mid = l + r >> 1; LL ret = 0; pushdn( u, l, r );
if ( ql <= mid ) ret += query( u << 1, l, mid, ql, qr );
if ( mid < qr ) ret += query( u << 1 | 1, mid + 1, r, ql, qr );
return ret;
}
} sgt;
struct LinkCutTree {
static const int MAXND = MAXN << 1;
int fa[MAXND + 5], ch[MAXND + 5][2];
int len[MAXND + 5], mnl[MAXND + 5], las[MAXND + 5], tag[MAXND + 5];
inline bool nroot( const int x ) {
return ch[fa[x]][0] == x || ch[fa[x]][1] == x;
}
inline void pushup( const int x ) {
mnl[x] = len[x];
if ( ch[x][0] ) chkmin( mnl[x], mnl[ch[x][0]] );
if ( ch[x][1] ) chkmin( mnl[x], mnl[ch[x][1]] );
}
inline void pushls( const int x, const int v ) { las[x] = tag[x] = v; }
inline void pushdn( const int x ) {
if ( tag[x] ) {
if ( ch[x][0] ) pushls( ch[x][0], tag[x] );
if ( ch[x][1] ) pushls( ch[x][1], tag[x] );
tag[x] = 0;
}
}
inline void rotate( const int x ) {
int y = fa[x], z = fa[y], k = ch[y][1] == x;
pushdn( y ), pushdn( x );
fa[x] = z; if ( nroot( y ) ) ch[z][ch[z][1] == y] = x;
ch[y][k] = ch[x][!k]; if ( ch[x][!k] ) fa[ch[x][!k]] = y;
pushup( ch[fa[y] = x][!k] = y ), pushup( x );
}
inline void splay( const int x ) {
static int y, z, stk[MAXN + 5];
for ( stk[y = 1] = z = x; nroot( z ); stk[++y] = z = fa[z] );
for ( ; y; pushdn( stk[y--] ) );
for ( ; nroot( x ); rotate( x ) ) {
if ( nroot( y = fa[x] ) ) {
rotate( x ^ y ^ ch[y][0] ^ ch[fa[y]][0] ? x : y );
}
}
}
inline void access( int x, const int r ) {
int t = x;
for ( int y = 0; x; x = fa[y = x] ) {
splay( x ), ch[x][1] = y, pushup( x );
if ( las[x] ) {
sgt.add( 1, 1, n,
las[x] - sam.mx[x] + 1, las[x] - mnl[x] + 1, -1 );
}
}
splay( t ), pushls( t, r );
sgt.add( 1, 1, n, r - sam.mx[t] + 1, r, 1 );
}
} lct;
int main() {
scanf( "%s", s + 1 ), n = strlen( s + 1 );
rep ( i, 1, m = rint() ) {
int l = rint(), r = rint();
ask[r].push_back( { l, i } );
}
rep ( i, 1, n ) sam.extend( i, s[i] - 'a' );
rep ( i, 1, sam.node ) {
lct.mnl[i] = lct.len[i] = sam.mx[lct.fa[i] = sam.fail[i]] + 1;
}
rep ( i, 1, n ) {
lct.access( sam.pos[i], i );
for ( PII q: ask[i] ) ans[q.se] = sgt.query( 1, 1, n, q.fi, i );
}
rep ( i, 1, m ) wint( ans[i] ), putchar( '\n' );
return 0;
}