「AHOI2013」 差异
知识点: 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
化下式子:
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}\) 之和。
发现有下列等价关系:
\]
\(\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)\)。
线段树太傻逼了,考虑单调栈。
发现有下列等价关系:
\]
即求 \(\operatorname{height}\) 每个区间的区间最小值之和。
经典问题,考虑 \(\operatorname{height}\) 作为最小值的区间的最大 左/右端 点,可单调栈维护。
答案即 \(\sum\limits_{i=2}^{n}(i-l_i)\times (r_i-i)\times \operatorname{height}_i\)。
注意区间长度不能为 1。
后缀树
考虑原始式子:
\]
这玩意长得很树上差分。
对于 \(S\) 的后缀树,\(\operatorname{lcp}\) 即为后缀树的 \(\operatorname{lca}\)。
上式等价于后缀树上所有后缀之间的距离。
对反串建 SAM,即得后缀树。
题目转化为:树上某一点是多少 表示后缀的节点 的 \(\operatorname{lca}\) 再乘上 \(dep\)。
记录子树大小, DP 实现即可。
爆零小技巧:线段树不一定只开 4 倍空间,当 \(n\) 到达 \(5\times 10^5\) 级别一定要小心。
代码实现
SA + 单调栈
这写法挺神仙的,感觉要重学单调栈。
//
/*
By:Luckyblock
*/
#include <cstdio>
#include <ctype.h>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
const int kMaxn = 5e5 + 10;
//=============================================================
char S[kMaxn];
int n, m, sa[kMaxn], rk[kMaxn << 1], oldrk[kMaxn << 1], height[kMaxn];
int cnt[kMaxn], id[kMaxn], rkid[kMaxn];
int top, st[kMaxn], l[kMaxn], r[kMaxn];
//=============================================================
inline int read() {
int f = 1, w = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void GetMax(int &fir, int sec) {
if (sec > fir) fir = sec;
}
void GetMin(int &fir, int sec) {
if (sec < fir) fir = sec;
}
int cmp(int x, int y, int w) {
return oldrk[x] == oldrk[y] &&
oldrk[x + w] == oldrk[y + w];
}
void GetHeight() {
for (int i = 1, k = 0; i <= n; ++ i) {
if (rk[i] == 1) k = 0;
else {
if (k > 0) k --;
int j = sa[rk[i] - 1];
while (i + k <= n && j + k <= n &&
S[i + k] == S[j + k]) {
++ k;
}
}
height[rk[i]] = k;
}
}
void SuffixSort() {
n = strlen(S + 1);
m = 1010;
for (int i = 1; i <= n; ++ i) cnt[rk[i] = S[i]] ++;
for (int i = 1; i <= m; ++ i) cnt[i] += cnt[i - 1];
for (int i = n; i; -- i) sa[cnt[rk[i]] --] = i;
for (int p, w = 1; w < n; w <<= 1) {
p = 0;
for (int i = n; i > n - w; -- i) id[++ p] = i;
for (int i = 1; i <= n; ++ i) {
if (sa[i] > w) id[++ p] = sa[i] - w;
}
memset(cnt, 0, sizeof (cnt));
for (int i = 1; i <= n; ++ i) cnt[rkid[i] = rk[id[i]]] ++;
for (int i = 1; i <= m; ++ i) cnt[i] += cnt[i - 1];
for (int i = n; i; -- i) sa[cnt[rkid[i]] --] = id[i];
std :: swap(rk, oldrk);
m = 0;
for (int i = 1; i <= n; ++ i) {
m += (cmp(sa[i], sa[i - 1], w) ^ 1);
rk[sa[i]] = m;
}
}
GetHeight();
}
//=============================================================
int main() {
scanf("%s", S + 1);
SuffixSort();
ll ans = 1ll * ((n - 1ll) * n / 2ll) * (n + 1ll) ;
st[(top = 1)] = 1;
for (int i = 2; i <= n; ++ i) {
while (top && height[st[top]] > height[i]) {
r[st[top]] = i;
top --;
}
l[i] = st[top];
st[++ top] = i;
}
while (top) r[st[top --]] = n + 1;
for (int i = 2; i <= n; ++ i) {
ans -= 2ll * (i - l[i]) * (r[i] - i) * height[i];
}
printf("%lld", ans);
return 0;
}
SA + 线段树
//知识点:SA
/*
By:Luckyblock
*/
#include <cstdio>
#include <ctype.h>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
#define lson (now_<<1)
#define rson (now_<<1|1)
const int kMaxn = 5e5 + 10;
//=============================================================
char S[kMaxn];
int n, m, sa[kMaxn], rk[kMaxn << 1], oldrk[kMaxn << 1], height[kMaxn];
int cnt[kMaxn], id[kMaxn], rkid[kMaxn];
ll size[kMaxn << 3], sum[kMaxn << 3];
bool tag[kMaxn << 3];
//=============================================================
inline int read() {
int f = 1, w = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void GetMax(int &fir, int sec) {
if (sec > fir) fir = sec;
}
void GetMin(int &fir, int sec) {
if (sec < fir) fir = sec;
}
int cmp(int x, int y, int w) {
return oldrk[x] == oldrk[y] &&
oldrk[x + w] == oldrk[y + w];
}
void GetHeight() {
for (int i = 1, k = 0; i <= n; ++ i) {
if (rk[i] == 1) k = 0;
else {
if (k > 0) k --;
int j = sa[rk[i] - 1];
while (i + k <= n && j + k <= n &&
S[i + k] == S[j + k]) {
++ k;
}
}
height[rk[i]] = k;
}
}
void SuffixSort() {
n = strlen(S + 1);
m = 1010;
for (int i = 1; i <= n; ++ i) cnt[rk[i] = S[i]] ++;
for (int i = 1; i <= m; ++ i) cnt[i] += cnt[i - 1];
for (int i = n; i; -- i) sa[cnt[rk[i]] --] = i;
for (int p, w = 1; w < n; w <<= 1) {
p = 0;
for (int i = n; i > n - w; -- i) id[++ p] = i;
for (int i = 1; i <= n; ++ i) {
if (sa[i] > w) id[++ p] = sa[i] - w;
}
memset(cnt, 0, sizeof (cnt));
for (int i = 1; i <= n; ++ i) cnt[rkid[i] = rk[id[i]]] ++;
for (int i = 1; i <= m; ++ i) cnt[i] += cnt[i - 1];
for (int i = n; i; -- i) sa[cnt[rkid[i]] --] = id[i];
std :: swap(rk, oldrk);
m = 0;
for (int i = 1; i <= n; ++ i) {
m += (cmp(sa[i], sa[i - 1], w) ^ 1);
rk[sa[i]] = m;
}
}
GetHeight();
}
void Pushdown(int now_) {
tag[lson] = tag[rson] = true;
size[lson] = size[rson] = 0;
sum[lson] = sum[rson] = 0;
tag[now_] = false;
}
void Pushup(int now_) {
size[now_] = size[lson] + size[rson];
sum[now_] = sum[lson] + sum[rson];
}
ll Delete(int now_, int L_, int R_, int ql_, int qr_) {
if (ql_ <= L_ && R_ <= qr_) {
ll ret = size[now_];
tag[now_] = true;
size[now_] = sum[now_] = 0ll;
return ret;
}
if(tag[now_]) Pushdown(now_);
int mid = (L_ + R_) >> 1;
ll ret = 0ll;
if (ql_ <= mid) ret += Delete(lson, L_, mid, ql_, qr_);
if (qr_ > mid) ret += Delete(rson, mid + 1, R_, ql_, qr_);
Pushup(now_);
return ret;
}
void Insert(int now_, int L_, int R_, int pos_, ll num) {
if (! num) return ;
if (L_ == R_) {
size[now_] += num;
sum[now_] += 1ll * num * (L_ - 1ll);
return ;
}
if (tag[now_]) Pushdown(now_);
int mid = (L_ + R_) >> 1;
if (pos_ <= mid) Insert(lson, L_, mid, pos_, num);
else Insert(rson, mid + 1, R_, pos_, num);
Pushup(now_);
}
//=============================================================
int main() {
scanf("%s", S + 1);
SuffixSort();
ll ans = 1ll * ((n - 1ll) * n / 2ll) * (n + 1ll) ;
for (int j = 2; j <= n; ++ j) {
ll num = Delete(1, 1, n + 1, height[j] + 2, n + 1);
Insert(1, 1, n + 1, height[j] + 1, num + 1);
ans -= 2ll * sum[1];
}
printf("%lld", ans);
return 0;
}
后缀树
咕咕咕,建议 Lg题解。
「AHOI2013」 差异的更多相关文章
- 「2013-9-5」Configure WingIDE for better display of East Asian Glyphs
很久没写软件配置相关的博客了.这次对于 WingIDE 在 Windows 下的字体配置,折腾了好一阵子,略曲折,也反映了「不清楚原理和背景的情况下,盲人摸象的效率低下是必然」这条放之四海而皆准的赤果 ...
- spring cloud 入门,看一个微服务框架的「五脏六腑」
Spring Cloud 是一个基于 Spring Boot 实现的微服务框架,它包含了实现微服务架构所需的各种组件. 注:Spring Boot 简单理解就是简化 Spring 项目的搭建.配置.组 ...
- 从 Spring Cloud 看一个微服务框架的「五脏六腑」
原文:https://webfe.kujiale.com/spring-could-heart/ Spring Cloud 是一个基于 Spring Boot 实现的微服务框架,它包含了实现微服务架构 ...
- 从 Spring Cloud 看一个微服务框架的「五脏六腑」(转)
Spring Cloud 是一个基于 Spring Boot 实现的微服务框架,它包含了实现微服务架构所需的各种组件. 本文将从 Spring Cloud 出发,分两小节讲述微服务框架的「五脏六腑」: ...
- 零元学Expression Blend 4 - Chapter 34 啊~!!我不要毛毛的感觉!-使用布局修整「UseLayoutRounding」
原文:零元学Expression Blend 4 - Chapter 34 啊~!!我不要毛毛的感觉!-使用布局修整「UseLayoutRounding」 本章将介绍UseLayoutRounding ...
- 零元学Expression Blend 4 - Chapter 9 用实例了解布局容器系列-「Canvas」
原文:零元学Expression Blend 4 - Chapter 9 用实例了解布局容器系列-「Canvas」 本系列将教大家以实做案例认识Blend 4 的布局容器,此章介绍的布局容器是Blen ...
- iOS 9,为前端世界都带来了些什么?「译」 - 高棋的博客
2015 年 9 月,Apple 重磅发布了全新的 iPhone 6s/6s Plus.iPad Pro 与全新的操作系统 watchOS 2 与 tvOS 9(是的,这货居然是第 9 版),加上已经 ...
- 「MoreThanJava」Java发展史及起航新世界
「MoreThanJava」 宣扬的是 「学习,不止 CODE」,本系列 Java 基础教程是自己在结合各方面的知识之后,对 Java 基础的一个总回顾,旨在 「帮助新朋友快速高质量的学习」. 当然 ...
- 「译」JUnit 5 系列:条件测试
原文地址:http://blog.codefx.org/libraries/junit-5-conditions/ 原文日期:08, May, 2016 译文首发:Linesh 的博客:「译」JUni ...
随机推荐
- java运行报错 has been compiled by a more recent version of the Java Runtime (class file version 55.0), this version of the Java Runtime only recognizes class file versions up to 52.0
解决方法: 解决办法: 在项目的属性里设置jdk版本,方法是右击项目-->properties-->java compiler --> Enable project specific ...
- day35前端基础之BOM和DOM
day35前端基础之BOM和DOM BOM操作 简介 BOM(Browser Object Model)是指浏览器对象模型,它使 JavaScript 有能力与浏览器进行"对话". ...
- 30个类手写Spring核心原理之环境准备(1)
本文节选自<Spring 5核心原理> 1 IDEA集成Lombok插件 1.1 安装插件 IntelliJ IDEA是一款非常优秀的集成开发工具,功能强大,而且插件众多.Lombok是开 ...
- 『学了就忘』Linux启动引导与修复 — 70、grub启动引导程序的配置文件说明
目录 1.grub中分区的表示方法 2.grub的配置文件 3.grub的配置文件内容说明 (1)grub的整体设置 (2)CentOS系统的启动设置 1.grub中分区的表示方法 在说grub启动引 ...
- RB-Tree深度探索
关联式容器就是通过key值来寻找value,这个和数据库很相像,为了提升查找效率,因此关联式容器底层大多数用红黑树或哈希表来实现. 红黑树是高度平衡的二叉树,它也被称为平衡二元搜索树. 如上所示,正常 ...
- 转 onSaveInstanceState()和onRestoreInstanceState()使用详解
转 https://www.jianshu.com/p/27181e2e32d2 背景 如果系统由于系统约束(而不是正常的应用程序行为)而破坏了Activity,那么尽管实际 Activity实例已经 ...
- GO 定时器NewTimer、NewTicker使用
package main import ( "fmt" "sync" "time" ) /** *ticker只要定义完成,从此刻开始计时, ...
- OpenStack之八: network服务(端口9696)
注意此处用的一个网络,暂时不用启动第二个网官网地址 https://docs.openstack.org/neutron/stein/install/controller-install-rdo.ht ...
- 01_ubantu国内软件源配置
查找自己版本对应的软件源 https://mirrors.tuna.tsinghua.edu.cn/help/ubuntu/ 以下为19.10版本清华大学的,个人100M的带宽,平均安装速度在600K ...
- Spring Cloud中,如何解决Feign整合Hystrix第一次请求失败的问题
Spring Cloud中,Feign和Ribbon在整合了Hystrix后,可能会出现首次调用失败的问题,要如何解决该问题呢? 造成该问题的原因 Hystrix默认的超时时间是1秒,如果超过这个时间 ...