\(\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. web.xml文件配置模板

    直接贴完整代码,当然,spring的核心控制器依赖包需要通过mean提前配置 <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.// ...

  2. Linux环境下的Docker的安装和部署、学习-一

    CentOS Docker 安装Docker支持以下的CentOS版本:CentOS 7 (64-bit)CentOS 6.5 (64-bit) 或更高的版本 前提条件目前,CentOS 仅发行版本中 ...

  3. idea环境下SpringBoot Web应用引入JSP

    1. 环境 开发环境:idea2019.3 jkd版本:1.8 springboot版本:2.6.2 2. 引入JSP的步骤 2.1 新建工程,引入依赖 这里只是解析jsp,因此只需要引入spring ...

  4. BERT-Pytorch版本代码pipline梳理

    最近在做BERT的fine-tune工作,记录一下阅读项目https://github.com/weizhepei/BERT-NER时梳理的训练pipline,该项目基于Google的Transfor ...

  5. STC8H开发(五): SPI驱动nRF24L01无线模块

    目录 STC8H开发(一): 在Keil5中配置和使用FwLib_STC8封装库(图文详解) STC8H开发(二): 在Linux VSCode中配置和使用FwLib_STC8封装库(图文详解) ST ...

  6. 游戏mod启动器原理

    基本原理 游戏程序会按一定顺序读取游戏文件夹根目录的文件. 所以我们制作mod和补丁的时候需要使得我们的文件先读取,从而使得后面读取到重复内容时候,游戏运行的内存中舍弃掉原本的文件. 游戏mod启动器 ...

  7. 探索新冠肺炎(COVID-19)对全球航班的影响

    Cesium中文网:http://cesiumcn.org/ | 国内快速访问:http://cesium.coinidea.com/ 随着今天从欧洲到美国的旅行限制生效,以及为了减缓新冠病毒的传播更 ...

  8. k8s 基于RBAC的认证、授权介绍和实践

    在K8S中,当我们试图通过API与集群资源交互时,必定经过集群资源管理对象入口kube-apiserver.显然不是随随便便来一个请求它都欢迎的,每个请求都需要经过合规检查,包括Authenticat ...

  9. 测试udp端口

    yum -y install nc 在a机器上执行: nc -ul 1080 在b机器上执行:nc -u 服务器ip 1080 a机器可以接收到报文则代表端口正常.

  10. 计算机网络-5-9-TCP拥塞控制

    TCP拥塞控制 拥塞控制的一般原理 在计算机网络中的链路容量(带宽),交换节点中的缓存和处理机等,都是网络的资源,在某段时间,若对网络中某一资源的需求超过该资源所能提供的可用部分,网络性能就会变坏,这 ...