\(\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. nvm安装vue-cli

    使用nvm可以更换nodejs版本.方便不同项目的切换 1.安装nvm(本人提供版本为1.1.9,当前最新) ① 到官网自行下载 https://github.com/coreybutler/nvm- ...

  2. Go语言系列- http编程和mysql

    http编程 一.Http协议  1. 什么是协议? 协议,是指通信的双方,在通信流程或内容格式上,共同遵守的标准.  2. 什么是http协议? http协议,是互联网中最常见的网络通信标准.  3 ...

  3. Itellij Idea使用

    图片如果损坏,点击链接查看 https://www.toutiao.com/i6491635946176381454/ 创建Maven项目 JDK环境 目前大多数IDE都没有集成JDK环境,IDEA也 ...

  4. JUC之线程池基础

    线程池 定义和方法 线程池的工作时控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出数量的线程排队等候,等待其他线程执行完成,再从队列中取出任 ...

  5. 系统信号SIGHUP、SIGQUIT、SIGTERM、SIGINT的场景

    SIGHUP:hong up 挂断.本信号在用户终端连接(正常或非正常)结束时发出, 通常是在终端的控制进程结束时, 通知同一session内的各个作业, 这时它们与控制终端不再关联.登录Linux时 ...

  6. fiddler-ios13以上信任证书后无法抓取https包

    直接下载fiddler插件原始是不兼容https://telerik-fiddler.s3.amazonaws.com/fiddler/addons/fiddlercertmaker.exe

  7. golang中goroutine协程调度器设计策略

    goroutine与线程 /* goroutine与线程1. 可增长的栈os线程一般都有固定的栈内存,通常为2MB,一个goroutine的在其声明周期开始时只有很小的栈(2KB),goroutine ...

  8. 总是记不住但又总是要用的css

    有没有经常遇到一些样式每次写都要用百度呢?我收集了一些我平时经常要用到的但又总是记不住的样式.有错误的地方欢迎指正.转载请注明出处. 一.设置input 的placeholder的字体样式 input ...

  9. 什么是VRRP?

    目录 一:什么是VRRP? 二:为什么需要VRRP 三:VRRP的工作原理 1.VRRP的三种状态 2.VRRP选举机制 3.VRRP工作原理 四:VRRP的详细工作过程如下: 五:VRRP应用场景 ...

  10. 关于笨蛋式病毒创作(CMD式)C++

    ​ 对于病毒创作,有好多种原因:有想装逼的,想盗取信息的...... 任何理由千奇百出,有的在下面评论出来. 对于病毒创作,我可是既会有不会,只会乱写,回头一看,全错了. 我今天写的这篇博客就是面向于 ...