Illublog我多想说再见啊

区间本质不同子串个数

Illu·2022-03-13 15:57·171 次阅读

区间本质不同子串个数

复盘 Pitiless0514 的 LCT ,找了一道好题做做。

Plus 版 这是 sol

Description#

给定长度为 n 的模式串, m 次询问,求区间本质不同子串个数。

n105,  m2105

Analysis#

没有修改,静态的,好,这样 SAM 就能派上用场了。

但是求区间内的话,我们发现好像也只能重构,顶多能把被包含的区间排除,没啥用。

可能直接 SAM 是有点毛病的,所以想考虑点其他东西。

比如说求区间不同颜色数,静态的,有这么一种解法:

  • 首先离线。

  • 类似一个扫描线的想法,定好右端点,从左往右扫,用一个线段树或者树状数组记录,每次加入一个颜色 colr 后,这个点的权值定位 1 ,取消这个颜色上一个出现的地方的权值。

  • 这样的话有查询右边界是 r 就可以找区间权值和,因为这样相同颜色只会算一次,所以没有问题。

Solution#

那其实这道题也能有类似的想法,但是每次新加进来一个字符可能会多很多种子串,咋办捏??

还是回归到 SAM 上,加入一个新字符之后会有一些字串对应的状态的 endpos 会多一个位置,并且这一定是在一条链上(还是顶到了根的那种)的状态,因为一些小性质(具体是什么不细讲了)。

然后发现越想越像树点染色,并且也能联想到 LCT 的 access 操作。

那如果我们用上 LCT ,在 access 的时候更新一下新出现的字符串就行了吧。

那那怎么更新呢??

既然我们是选择了数据结构算区间和,又封死了右边界,所以我们就要在区间和上体现出来初始位置,于是就把在 j 出现的字符串的贡献标记到 jlen+1 上。

更新的话我们就要取消在上次出现位置标记的地方的贡献,然后转到新地方再标记,这样的话因为区间的特殊性不管怎么样查询都不会算漏。

因为这些如果要出现就全部是连续出现,所以可以区间修改。因为我们已经把 SAM 搬到 LCT 上面了,所以我们在 access 的时候对于一个状态取消上次的贡献,最后全局加就行了。

线段树和树状数组都可以捏。

Code#

Code
Copy
/* */ #include using namespace std; typedef long long ll; const int N = 4e5 + 10, M = 2e6 + 10; int n, pos[N]; ll ans[N]; struct mdzz { int l, r, id; bool operator < (const mdzz &it) const { return r < it.r; } } a[N]; inline int read() { char ch = getchar(); int s = 0, w = 1; while (!isdigit(ch)) {if (ch == '-') w = -1; ch = getchar();} while (isdigit(ch)) {s = (s << 3) + (s << 1) + (ch ^ 48); ch = getchar();} return s * w; } struct SAM { int n, cnt, las, len[N], link[N], ch[N][26]; char s[N]; int tong[N], rk[N]; inline void init() {cnt = las = 1; memset(ch[1], 0, sizeof(ch[1]));} inline void SAM_stru(int c) { int cur = ++cnt, p = las; memset(ch[cur], 0, sizeof(ch[cur])); las = cur; len[cur] = len[p] + 1; while (p && !ch[p][c]) ch[p][c] = cur, p = link[p]; if (!p) {link[cur] = 1; return ;} int q = ch[p][c]; if (len[p] + 1 == len[q]) {link[cur] = q; return ;} int clo = ++cnt; link[clo] = link[q]; len[clo] = len[p] + 1; link[q] = link[cur] = clo; memcpy(ch[clo], ch[q], sizeof(ch[clo])); while (p && ch[p][c] == q) ch[p][c] = clo, p = link[p]; } inline void Tong_sort() { for (int i = 1; i <= cnt; ++i) ++tong[len[i]]; for (int i = 1; i <= cnt; ++i) tong[i] += tong[i - 1]; for (int i = 1; i <= cnt; ++i) rk[tong[len[i]]--] = i; } } s; struct SGT { ll c[M], d[M]; inline int lowbit(int k) {return k & (-k);} inline void add(int x, int y) { for (int p = x; x <= s.n; x += lowbit(x)) c[x] += y, d[x] += p * y; } inline ll ask(int x) { ll tmp = 0, res = 0, p = x; for (; x; x -= lowbit(x)) tmp += c[x], res += d[x]; return tmp * (p + 1) - res; } inline void add(int l, int r, int k) {add(l, k); add(r + 1, -k);} } seg; struct LCT { #define ls(x) ch[x][0] #define rs(x) ch[x][1] int ch[N][2], fa[N], xr[N], cov[N], val[N], len[N], top, sta[N]; inline void build() { val[0] = 1e9; for (int i = 1; i <= s.cnt; ++i) { fa[i] = s.link[i]; val[i] = len[i] = s.len[fa[i]] + 1; xr[i] = cov[i] = ls(i) = rs(i) = 0; } } inline void pushcov(int x, int k) {xr[x] = cov[x] = k;} inline void pushup(int x) { val[x] = min(len[x], min(val[ls(x)], val[rs(x)])); } inline void pushdown(int x) { if (cov[x]) { if (ls(x)) pushcov(ls(x), cov[x]); if (rs(x)) pushcov(rs(x), cov[x]); cov[x] = 0; } } inline bool nroot(int x) {return ls(fa[x]) == x || rs(fa[x]) == x;} inline void rotate(int x) { int y = fa[x], z = fa[y], k = rs(y) == x, w = ch[x][!k]; if (nroot(y)) ch[z][rs(z) == y] = x; ch[x][!k] = y; ch[y][k] = w; if (w) fa[w] = y; fa[y] = x; fa[x] = z; pushup(y); pushup(x); } inline void splay(int x) { int y = x, z; top = 0; sta[++top] = y; while (nroot(y)) sta[++top] = y = fa[y]; while (top) pushdown(sta[top--]); while (nroot(x)) { y = fa[x]; z = fa[y]; if (nroot(y)) rotate((ls(y) == x) ^ (ls(z) == y) ? x : y); rotate(x); } } inline void access(int x, int k) { int y = 0; while (x) { splay(x); if (xr[x]) seg.add(xr[x] - s.len[x] + 1, xr[x] - val[x] + 1, -1); rs(x) = y; y = x, x = fa[x]; } pushcov(y, k); seg.add(1, k, 1); } #undef ls #undef rs } lct; int main() { scanf("%s", s.s + 1); s.n = strlen(s.s + 1); s.init(); for (int i = 1; i <= s.n; ++i) { s.SAM_stru(s.s[i] - 'a'); pos[i] = s.las; } s.Tong_sort(); n = read(); for (int i = 1; i <= n; ++i) { a[i] = (mdzz) {read(), read(), i}; } sort(a + 1, a + 1 + n); lct.build(); for (int i = 1, j = 1; i <= n; ++i) { while (j <= a[i].r) lct.access(pos[j], j), ++j; ans[a[i].id] = seg.ask(a[i].r) - seg.ask(a[i].l - 1); } for (int i = 1; i <= n; ++i) printf("%lld\n", ans[i]); return 0; }
posted @   Illusory_dimes  阅读(171)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示
目录