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

Solution -「洛谷 P6292」区间本质不同子串个数的更多相关文章

  1. Solution -「洛谷 P4372」Out of Sorts P

    \(\mathcal{Description}\)   OurOJ & 洛谷 P4372(几乎一致)   设计一个排序算法,设现在对 \(\{a_n\}\) 中 \([l,r]\) 内的元素排 ...

  2. Note/Solution -「洛谷 P5158」「模板」多项式快速插值

    \(\mathcal{Description}\)   Link.   给定 \(n\) 个点 \((x_i,y_i)\),求一个不超过 \(n-1\) 次的多项式 \(f(x)\),使得 \(f(x ...

  3. Solution -「洛谷 P4198」楼房重建

    \(\mathcal{Description}\)   Link.   给定点集 \(\{P_n\}\),\(P_i=(i,h_i)\),\(m\) 次修改,每次修改某个 \(h_i\),在每次修改后 ...

  4. Solution -「洛谷 P4194」矩阵

    \(\mathcal{Description}\)   Link.   给定一个 \(n\times m\) 的矩阵 \(A\),构造一个 \(n\times m\) 的矩阵 \(B\),s.t. \ ...

  5. Solution -「洛谷 P5787」「模板」二分图(线段树分治)

    \(\mathcal{Description}\)   Link.    \(n\) 个结点的图,\(m\) 条形如 \((u,v,l,r)\) 的边,表示一条连接 \(u\) 和 \(v\) 的无向 ...

  6. Solution -「洛谷 P6577」「模板」二分图最大权完美匹配

    \(\mathcal{Description}\)   Link.   给定二分图 \(G=(V=X\cup Y,E)\),\(|X|=|Y|=n\),边 \((u,v)\in E\) 有权 \(w( ...

  7. Solution -「洛谷 P6021」洪水

    \(\mathcal{Description}\)   Link.   给定一棵 \(n\) 个点的带点权树,删除 \(u\) 点的代价是该点点权 \(a_u\).\(m\) 次操作: 修改单点点权. ...

  8. Solution -「洛谷 P4719」「模板」"动态 DP" & 动态树分治

    \(\mathcal{Description}\)   Link.   给定一棵 \(n\) 个结点的带权树,\(m\) 次单点点权修改,求出每次修改后的带权最大独立集.   \(n,m\le10^5 ...

  9. Solution -「洛谷 P5236」「模板」静态仙人掌

    \(\mathcal{Description}\)   Link.   给定一个 \(n\) 个点 \(m\) 条边的仙人掌,\(q\) 组询问两点最短路.   \(n,q\le10^4\),\(m\ ...

随机推荐

  1. android studio 获取 SHA1 值

    1. 生成密钥文件 2.找到控制台 输入指令 cd c: cd C:\Users\[当前登录的用户文件夹]\.android 如 cd C:\Users\cenxi\.android 就是这里 然后输 ...

  2. 第10组-Alpha冲刺 总结

    1.基本情况 组长博客链接:https://www.cnblogs.com/cpandbb/p/14007413.html 答辩总结: ·产品偏离了最开始的方向,地图和刷一刷功能做得没那么好,外卖订单 ...

  3. 基于Apache Hudi + Flink的亿级数据入湖实践

    本次分享分为5个部分介绍Apache Hudi的应用与实践 实时数据落地需求演进 基于Spark+Hudi的实时数据落地应用实践 基于Flink自定义实时数据落地实践 基于Flink+Hudi的应用实 ...

  4. SQLAlchemy完全入门

    最近想要学习SQLAlchemy, 发现网上的中文文档大多是机翻的, 读起来特别变扭, 因此对照着最新的英文文档梳理了一遍, 写下来记录一下 目前SQLAlchemy的版本为1.4.x, 风格处于1. ...

  5. Educational Codeforces Round 117 (Rated for Div. 2)

    Educational Codeforces Round 117 (Rated for Div. 2) A. Distance https://codeforces.com/contest/1612/ ...

  6. Golang 常见设计模式之选项模式

    熟悉 Python 开发的同学都知道,Python 有默认参数的存在,使得我们在实例化一个对象的时候,可以根据需要来选择性的覆盖某些默认参数,以此来决定如何实例化对象.当一个对象有多个默认参数时,这个 ...

  7. POJ 3617 Best Cow Line (字典序最小问题 & 贪心)

    原题链接:http://poj.org/problem?id=3617 问题梗概:给定长度为 的字符串 , 要构造一个长度为 的字符串 .起初, 是一个空串,随后反复进行下列任意操作. 从 的头部删除 ...

  8. 【Java】获取两个字符串中最大相同子串

    题目 获取两个字符串中最大相同子串 前提 两个字符串中只有一个最大相同子串 解决方案 public class StringDemo { public static void main(String[ ...

  9. 学习AJAX必知必会(1)~Ajax

    一.ajax(Asynchronous JavaScript And XML,即异步的 JS 和 XML) 1.通过 AJAX 可以在浏览器中向服务器发送异步请求实现无刷新获取数据. 2.优势:无刷新 ...

  10. vue和react 相似和区别

    相似之处 他们都是JavaScript的UI框架,专注于创造前端的富应用 不同于早期的JavaScript框架"功能齐全",Reat与Vue只有框架的骨架,其他的功能如路由.状态管 ...