知识点: SA,线段树,单调栈

原题面 Loj Luogu


题意简述

给定一长度为 \(n\) 的字符串 \(S\),令 \(T_i\) 表示从第 \(i\) 个字符开始的后缀,求:

\[\sum_{1\le i<j\le n}\{\operatorname{len}(T_i) +\operatorname{len}(T_j) - 2\times \operatorname{lcp} (T_i,T_j)\}
\]

\(\operatorname{len}(a)\) 表示字符串 \(a\) 的长度,\(\operatorname{lcp}(a,b)\) 表示字符串 \(a,b\) 的最长公共前缀。

分析题意

SA

化下式子:

\[\begin{aligned}
ans &= \sum_{1\le i<j\le n}\{\operatorname{len}(T_i) +\operatorname{len}(T_j) - 2\times \operatorname{lcp} (T_i,T_j)\}\\
&= \sum_{1\le i<j\le n}\{(n-i+1) +(n-j+1) - 2\times \operatorname{lcp} (T_i,T_j)\}\\
&= \dfrac{(n-1)\times n \times (n+1)}{2} + 2\sum_{1\le i<j\le n}\operatorname{lcp} (T_i,T_j)
\end{aligned}\]

考虑如何快速求后一半,即所有 \(\operatorname{lcp}\) 之和。

发现有下列等价关系:

\[\sum_{1\le i<j\le n}\operatorname{lcp} (T_i,T_j) = \sum_{1\le i<j\le n}\operatorname{lcp}(T_{sa_i}, T_{sa_j})
\]

\(\operatorname{lcp}(a,b) = \operatorname{lcp}(b,a)\),枚举 \(sa\) 一定不会重也不会漏。

类似这题的套路:「HAOI2016」找相同字符

考虑枚举 \(sa_j\),用权值线段树维护 \(sa_i (i<j)\) 的不同长度的 \(\operatorname{lcp}(sa_i, sa_j)\) 的数量。

引理:\(\forall 1\le i < j\le n,\, \operatorname{lcp}(sa_i,sa_j) = \min\limits_{k=i+1}^j\{\operatorname{height_k}\}\)。

模拟引理,当 \(j+1\) 时将权值线段树中所有 \(>\operatorname{height}_{j+1}\) 的元素删除,并添加相同个数个 元素 \(\operatorname{height}_{j+1}\)。

添加一个 \(\operatorname{height}_{j+1}\),代表新增的 \(sa_j\) 的贡献。

贡献求和即可。

总复杂度 \(O(n\log n)\)。


线段树太傻逼了,考虑单调栈。

发现有下列等价关系:

\[\sum_{1\le i<j\le n}\operatorname{lcp}(T_{sa_i}, T_{sa_j}) = \sum_{1\le i<j\le n}\min_{k=i+1}^{j}\{\operatorname{height}_k\}
\]

即求 \(\operatorname{height}\) 每个区间的区间最小值之和。

经典问题,考虑 \(\operatorname{height}\) 作为最小值的区间的最大 左/右端 点,可单调栈维护。

答案即 \(\sum\limits_{i=2}^{n}(i-l_i)\times (r_i-i)\times \operatorname{height}_i\)。

注意区间长度不能为 1。


后缀树

考虑原始式子:

\[\sum_{1\le i<j\le n}\{\operatorname{len}(T_i) +\operatorname{len}(T_j) - 2\times \operatorname{lcp} (T_i,T_j)\}
\]

这玩意长得很树上差分。

对于 \(S\) 的后缀树,\(\operatorname{lcp}\) 即为后缀树的 \(\operatorname{lca}\)。

上式等价于后缀树上所有后缀之间的距离。

对反串建 SAM,即得后缀树。

题目转化为:树上某一点是多少 表示后缀的节点 的 \(\operatorname{lca}\) 再乘上 \(dep\)。

记录子树大小, DP 实现即可。


爆零小技巧:线段树不一定只开 4 倍空间,当 \(n\) 到达 \(5\times 10^5\) 级别一定要小心。


代码实现


SA + 单调栈

这写法挺神仙的,感觉要重学单调栈。

  1. //
  2. /*
  3. By:Luckyblock
  4. */
  5. #include <cstdio>
  6. #include <ctype.h>
  7. #include <cstring>
  8. #include <iostream>
  9. #include <algorithm>
  10. #define ll long long
  11. const int kMaxn = 5e5 + 10;
  12. //=============================================================
  13. char S[kMaxn];
  14. int n, m, sa[kMaxn], rk[kMaxn << 1], oldrk[kMaxn << 1], height[kMaxn];
  15. int cnt[kMaxn], id[kMaxn], rkid[kMaxn];
  16. int top, st[kMaxn], l[kMaxn], r[kMaxn];
  17. //=============================================================
  18. inline int read() {
  19. int f = 1, w = 0; char ch = getchar();
  20. for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
  21. for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  22. return f * w;
  23. }
  24. void GetMax(int &fir, int sec) {
  25. if (sec > fir) fir = sec;
  26. }
  27. void GetMin(int &fir, int sec) {
  28. if (sec < fir) fir = sec;
  29. }
  30. int cmp(int x, int y, int w) {
  31. return oldrk[x] == oldrk[y] &&
  32. oldrk[x + w] == oldrk[y + w];
  33. }
  34. void GetHeight() {
  35. for (int i = 1, k = 0; i <= n; ++ i) {
  36. if (rk[i] == 1) k = 0;
  37. else {
  38. if (k > 0) k --;
  39. int j = sa[rk[i] - 1];
  40. while (i + k <= n && j + k <= n &&
  41. S[i + k] == S[j + k]) {
  42. ++ k;
  43. }
  44. }
  45. height[rk[i]] = k;
  46. }
  47. }
  48. void SuffixSort() {
  49. n = strlen(S + 1);
  50. m = 1010;
  51. for (int i = 1; i <= n; ++ i) cnt[rk[i] = S[i]] ++;
  52. for (int i = 1; i <= m; ++ i) cnt[i] += cnt[i - 1];
  53. for (int i = n; i; -- i) sa[cnt[rk[i]] --] = i;
  54. for (int p, w = 1; w < n; w <<= 1) {
  55. p = 0;
  56. for (int i = n; i > n - w; -- i) id[++ p] = i;
  57. for (int i = 1; i <= n; ++ i) {
  58. if (sa[i] > w) id[++ p] = sa[i] - w;
  59. }
  60. memset(cnt, 0, sizeof (cnt));
  61. for (int i = 1; i <= n; ++ i) cnt[rkid[i] = rk[id[i]]] ++;
  62. for (int i = 1; i <= m; ++ i) cnt[i] += cnt[i - 1];
  63. for (int i = n; i; -- i) sa[cnt[rkid[i]] --] = id[i];
  64. std :: swap(rk, oldrk);
  65. m = 0;
  66. for (int i = 1; i <= n; ++ i) {
  67. m += (cmp(sa[i], sa[i - 1], w) ^ 1);
  68. rk[sa[i]] = m;
  69. }
  70. }
  71. GetHeight();
  72. }
  73. //=============================================================
  74. int main() {
  75. scanf("%s", S + 1);
  76. SuffixSort();
  77. ll ans = 1ll * ((n - 1ll) * n / 2ll) * (n + 1ll) ;
  78. st[(top = 1)] = 1;
  79. for (int i = 2; i <= n; ++ i) {
  80. while (top && height[st[top]] > height[i]) {
  81. r[st[top]] = i;
  82. top --;
  83. }
  84. l[i] = st[top];
  85. st[++ top] = i;
  86. }
  87. while (top) r[st[top --]] = n + 1;
  88. for (int i = 2; i <= n; ++ i) {
  89. ans -= 2ll * (i - l[i]) * (r[i] - i) * height[i];
  90. }
  91. printf("%lld", ans);
  92. return 0;
  93. }

SA + 线段树

  1. //知识点:SA
  2. /*
  3. By:Luckyblock
  4. */
  5. #include <cstdio>
  6. #include <ctype.h>
  7. #include <cstring>
  8. #include <iostream>
  9. #include <algorithm>
  10. #define ll long long
  11. #define lson (now_<<1)
  12. #define rson (now_<<1|1)
  13. const int kMaxn = 5e5 + 10;
  14. //=============================================================
  15. char S[kMaxn];
  16. int n, m, sa[kMaxn], rk[kMaxn << 1], oldrk[kMaxn << 1], height[kMaxn];
  17. int cnt[kMaxn], id[kMaxn], rkid[kMaxn];
  18. ll size[kMaxn << 3], sum[kMaxn << 3];
  19. bool tag[kMaxn << 3];
  20. //=============================================================
  21. inline int read() {
  22. int f = 1, w = 0; char ch = getchar();
  23. for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
  24. for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  25. return f * w;
  26. }
  27. void GetMax(int &fir, int sec) {
  28. if (sec > fir) fir = sec;
  29. }
  30. void GetMin(int &fir, int sec) {
  31. if (sec < fir) fir = sec;
  32. }
  33. int cmp(int x, int y, int w) {
  34. return oldrk[x] == oldrk[y] &&
  35. oldrk[x + w] == oldrk[y + w];
  36. }
  37. void GetHeight() {
  38. for (int i = 1, k = 0; i <= n; ++ i) {
  39. if (rk[i] == 1) k = 0;
  40. else {
  41. if (k > 0) k --;
  42. int j = sa[rk[i] - 1];
  43. while (i + k <= n && j + k <= n &&
  44. S[i + k] == S[j + k]) {
  45. ++ k;
  46. }
  47. }
  48. height[rk[i]] = k;
  49. }
  50. }
  51. void SuffixSort() {
  52. n = strlen(S + 1);
  53. m = 1010;
  54. for (int i = 1; i <= n; ++ i) cnt[rk[i] = S[i]] ++;
  55. for (int i = 1; i <= m; ++ i) cnt[i] += cnt[i - 1];
  56. for (int i = n; i; -- i) sa[cnt[rk[i]] --] = i;
  57. for (int p, w = 1; w < n; w <<= 1) {
  58. p = 0;
  59. for (int i = n; i > n - w; -- i) id[++ p] = i;
  60. for (int i = 1; i <= n; ++ i) {
  61. if (sa[i] > w) id[++ p] = sa[i] - w;
  62. }
  63. memset(cnt, 0, sizeof (cnt));
  64. for (int i = 1; i <= n; ++ i) cnt[rkid[i] = rk[id[i]]] ++;
  65. for (int i = 1; i <= m; ++ i) cnt[i] += cnt[i - 1];
  66. for (int i = n; i; -- i) sa[cnt[rkid[i]] --] = id[i];
  67. std :: swap(rk, oldrk);
  68. m = 0;
  69. for (int i = 1; i <= n; ++ i) {
  70. m += (cmp(sa[i], sa[i - 1], w) ^ 1);
  71. rk[sa[i]] = m;
  72. }
  73. }
  74. GetHeight();
  75. }
  76. void Pushdown(int now_) {
  77. tag[lson] = tag[rson] = true;
  78. size[lson] = size[rson] = 0;
  79. sum[lson] = sum[rson] = 0;
  80. tag[now_] = false;
  81. }
  82. void Pushup(int now_) {
  83. size[now_] = size[lson] + size[rson];
  84. sum[now_] = sum[lson] + sum[rson];
  85. }
  86. ll Delete(int now_, int L_, int R_, int ql_, int qr_) {
  87. if (ql_ <= L_ && R_ <= qr_) {
  88. ll ret = size[now_];
  89. tag[now_] = true;
  90. size[now_] = sum[now_] = 0ll;
  91. return ret;
  92. }
  93. if(tag[now_]) Pushdown(now_);
  94. int mid = (L_ + R_) >> 1;
  95. ll ret = 0ll;
  96. if (ql_ <= mid) ret += Delete(lson, L_, mid, ql_, qr_);
  97. if (qr_ > mid) ret += Delete(rson, mid + 1, R_, ql_, qr_);
  98. Pushup(now_);
  99. return ret;
  100. }
  101. void Insert(int now_, int L_, int R_, int pos_, ll num) {
  102. if (! num) return ;
  103. if (L_ == R_) {
  104. size[now_] += num;
  105. sum[now_] += 1ll * num * (L_ - 1ll);
  106. return ;
  107. }
  108. if (tag[now_]) Pushdown(now_);
  109. int mid = (L_ + R_) >> 1;
  110. if (pos_ <= mid) Insert(lson, L_, mid, pos_, num);
  111. else Insert(rson, mid + 1, R_, pos_, num);
  112. Pushup(now_);
  113. }
  114. //=============================================================
  115. int main() {
  116. scanf("%s", S + 1);
  117. SuffixSort();
  118. ll ans = 1ll * ((n - 1ll) * n / 2ll) * (n + 1ll) ;
  119. for (int j = 2; j <= n; ++ j) {
  120. ll num = Delete(1, 1, n + 1, height[j] + 2, n + 1);
  121. Insert(1, 1, n + 1, height[j] + 1, num + 1);
  122. ans -= 2ll * sum[1];
  123. }
  124. printf("%lld", ans);
  125. return 0;
  126. }

后缀树

咕咕咕,建议 Lg题解

「AHOI2013」 差异的更多相关文章

  1. 「2013-9-5」Configure WingIDE for better display of East Asian Glyphs

    很久没写软件配置相关的博客了.这次对于 WingIDE 在 Windows 下的字体配置,折腾了好一阵子,略曲折,也反映了「不清楚原理和背景的情况下,盲人摸象的效率低下是必然」这条放之四海而皆准的赤果 ...

  2. spring cloud 入门,看一个微服务框架的「五脏六腑」

    Spring Cloud 是一个基于 Spring Boot 实现的微服务框架,它包含了实现微服务架构所需的各种组件. 注:Spring Boot 简单理解就是简化 Spring 项目的搭建.配置.组 ...

  3. 从 Spring Cloud 看一个微服务框架的「五脏六腑」

    原文:https://webfe.kujiale.com/spring-could-heart/ Spring Cloud 是一个基于 Spring Boot 实现的微服务框架,它包含了实现微服务架构 ...

  4. 从 Spring Cloud 看一个微服务框架的「五脏六腑」(转)

    Spring Cloud 是一个基于 Spring Boot 实现的微服务框架,它包含了实现微服务架构所需的各种组件. 本文将从 Spring Cloud 出发,分两小节讲述微服务框架的「五脏六腑」: ...

  5. 零元学Expression Blend 4 - Chapter 34 啊~!!我不要毛毛的感觉!-使用布局修整「UseLayoutRounding」

    原文:零元学Expression Blend 4 - Chapter 34 啊~!!我不要毛毛的感觉!-使用布局修整「UseLayoutRounding」 本章将介绍UseLayoutRounding ...

  6. 零元学Expression Blend 4 - Chapter 9 用实例了解布局容器系列-「Canvas」

    原文:零元学Expression Blend 4 - Chapter 9 用实例了解布局容器系列-「Canvas」 本系列将教大家以实做案例认识Blend 4 的布局容器,此章介绍的布局容器是Blen ...

  7. iOS 9,为前端世界都带来了些什么?「译」 - 高棋的博客

    2015 年 9 月,Apple 重磅发布了全新的 iPhone 6s/6s Plus.iPad Pro 与全新的操作系统 watchOS 2 与 tvOS 9(是的,这货居然是第 9 版),加上已经 ...

  8. 「MoreThanJava」Java发展史及起航新世界

    「MoreThanJava」 宣扬的是 「学习,不止 CODE」,本系列 Java 基础教程是自己在结合各方面的知识之后,对 Java 基础的一个总回顾,旨在 「帮助新朋友快速高质量的学习」. 当然 ...

  9. 「译」JUnit 5 系列:条件测试

    原文地址:http://blog.codefx.org/libraries/junit-5-conditions/ 原文日期:08, May, 2016 译文首发:Linesh 的博客:「译」JUni ...

随机推荐

  1. 逻辑学与Prolog学习笔记

    int a = 3 + 5; 很自然.如果Matrix a, b要加呢?没有运算符重载,a + b是不行的,只能add(a, b). int a = add(3, 5)也行.如果函数名可以用+呢?+( ...

  2. Hive(七)【内置函数】

    目录 一.系统内置函数 1.查看系统自带内置函数 2.查看函数的具体用法 二.常用内置函数 1.数学函数 round 2.字符函数 split concat concat_ws lower,upper ...

  3. vim使用配置(转)

    在终端下使用vim进行编辑时,默认情况下,编辑的界面上是没有行号的.语法高亮度显示.智能缩进等功能的. 为了更好的在vim下进行工作,需要手动配置一个配置文件: .vimrc 在启动vim时,当前用户 ...

  4. 单元测试(Jest 和 Mocha)

    Vue CLI 拥有通过 Jest 或 Mocha 进行单元测试的内置选项. Jest 是功能最全的测试运行器.它所需的配置是最少的,默认安装了 JSDOM,内置断言且命令行的用户体验非常好.不过你需 ...

  5. linux环境下安装jdk,tomcat

    一.安装tomcat 1.使用docker安装(你得linux服务器上已经安装了docker) 1)执行命令: docker search tomcat; 2)选择第一个镜像进行下载,执行命令:doc ...

  6. PowerDotNet平台化软件架构设计与实现系列(06):定时任务调度平台

    定时任务是后端系统开发中少不了的一个基本必备技能. 传统的实现定时任务的方式有很多种,比如直接使用操作系统的Timer和TaskSchedule,或者基于Quartz.HangFire.xxl-job ...

  7. 【Linux】【CentOS】【FTP】FTP服务器安装与配置(vsftpd、lftp)

    [初次学习.配置的笔记,如有不当,欢迎在评论区纠正 -- 萌狼蓝天 @ 2021-12-02] 基本概念 FTP访问方式 实体账号:本地账户 来宾账户:guest 匿名登录:anonymous fp ...

  8. 35、搜索插入位置 | 算法(leetode,附思维导图 + 全部解法)300题

    零 标题:算法(leetode,附思维导图 + 全部解法)300题之(35)搜索插入位置 一 题目描述 二 解法总览(思维导图) 三 全部解法 1 方案1 1)代码: // 方案1 "无视要 ...

  9. vue+element项目中动态表格合并

    需求:elementui里的table虽然有合并函数(:span-method),单基本都是设置固定值合并.现在有一个树型结构的数据,要求我们将里面的某个list和其他属性一起展开展示,并且list中 ...

  10. Mybatis中对象关系映射

    在实际开发中,实体类之间有一对一.一对多.多对多的关系,所以需要正确配置它们对应关系,Mybatis通过配置文件能够从数据库中获取列数据后自动封装成对象. 如:一个订单Orders类对应一个用户Use ...