\(\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. 基于CentOS6.5-Hadoop2.7.3-hive-2.1.1安装sqoop1.4.7

    注:图片如果损坏,点击文章链接:https://www.toutiao.com/i6627736198431375879/ 系统版本,Hadoop已安装完成.链接<CentOS6.5下安装Had ...

  2. 【爬虫】从零开始使用 Scrapy

    一. 概述 最近有一个爬虫相关的需求,需要使用 scrapy 框架来爬取数据,所以学习了一下这个非常强大的爬虫框架,这里将自己的学习过程记录下来,希望对有同样需求的小伙伴提供一些帮助. 本文主要从下面 ...

  3. 《设计模式面试小炒》策略和工厂模式替代业务场景中复杂的ifelse

    <设计模式面试小炒>策略和工厂模式替代业务场景中复杂的ifelse 我是肥哥,一名不专业的面试官! 我是囧囧,一名积极找工作的小菜鸟! 囧囧表示:小白面试最怕的就是面试官问的知识点太笼统, ...

  4. Mybatis 学习记录

    1.先放上mybatis官网地址: https://mybatis.org/mybatis-3/zh/index.html 2.mybatis源码和有关包下载地址(GitHub): https://g ...

  5. 【解决了一个小问题】golang build中因为缓存文件损坏导致的编译错误

    编译的过程中出现了一个吓人的错误: GOROOT=C:\Go #gosetup GOPATH=C:\Users\ahfuzhang\go #gosetup C:\Go\bin\go.exe mod t ...

  6. selenium获取cookies并持久化登陆

    selenium获取cookies并持久化登陆 需求背景: ​ 这几天需要写一个接口,用来批量上传数据,最开始考虑的是 UI 自动化,然后选值的时候自动化难以判别,最终选择 接口 自动化. ​ 然后操 ...

  7. golang中结构体当做函数参数或函数返回值都会被拷贝

    1. 结构体做函数的参数或返回值时,都会被重新拷贝一份如果不想拷贝,可以传递结构体指针 package main import "fmt" type Person struct { ...

  8. gin中使用路由组

    package main import ( "github.com/gin-gonic/gin" ) func main() { router := gin.Default() / ...

  9. BUGKU-Misc 成果狗成果狗

    下载下来可以得到一张图片 成果真好看 放到kali里面用binwalk查看有没有隐藏文件,发现这里面有两张图片 然后可以拖到winhex或者010里面把两张图片分离出来,可以分离出1.jpg和54.j ...

  10. Gc如何判断对象可以被回收?

    Gc如何判断对象可以被回收? 1 引用计数器 引用计数法的算法思路:给对象增加一个引用计数器,每当对象增加一个引用计数器+1,失去一个引用-1,所以当计数器是0的时候对象就没有引用了,就会被认为可回收 ...