Live2D

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;
}

posted @ 2021-05-20 22:15  Rainybunny  阅读(131)  评论(1编辑  收藏  举报