「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 ...
随机推荐
- Pytorch学习笔记08----优化器算法Optimizer详解(SGD、Adam)
1.优化器算法简述 首先来看一下梯度下降最常见的三种变形 BGD,SGD,MBGD,这三种形式的区别就是取决于我们用多少数据来计算目标函数的梯度,这样的话自然就涉及到一个 trade-off,即参数更 ...
- Java项目发现==顺手改成equals之后,会发生什么?
最近发生一件很尴尬的事情,在维护一个 Java 项目的时候,发现有使用 == 来比较两个对象的属性, 于是顺手就把 == 改成了 equals.悲剧发生...... == 和 equals 的区别 = ...
- 商业爬虫学习笔记day3
一. 付费代理发送请求的两种方式 第一种方式: (1)代理ip,形式如下: money_proxy = {"http":"username:pwd@192.168.12. ...
- 商业爬虫学习笔记day8-------json的使用
一. 简介 JSON,全称为JavaScript Object Notation(JavaScript对象标记),它通过对象和数组的组合来表示数据,是一种轻量级的数据交换格式.它基于 ECMAScri ...
- 命令行方式运行hadoop程序
1,写一个java代码.*.java.(这里从example 拷贝一个过来作为测试) cp src/examples/org/apache/hadoop/examples/WordCount.java ...
- 1945-祖安say hello-string
1 #include<bits/stdc++.h> 2 char str[100][40]; 3 char s[1005]; 4 5 int remark[2000][2] = { 0 } ...
- 【C/C++】最长无重复子数组
题目描述 给定一个数组arr,返回arr的最长无重复元素子数组的长度,无重复指的是所有数字都不相同. 子数组是连续的,比如[1,2,3,4,5]的子数组有[1,2],[2,3,4]等等,但是[1,3, ...
- vue cli3.0 首次加载优化
项目经理要求做首页加载优化,打包后从十几兆优化到两兆多,记下来怕下次忘记 运行report脚本 可看到都加载了那些内容,在从dist文件中index.html 查看首次加载都加载了那些东西,如下图:然 ...
- Mysql配置文件 扩展详细配置
目录 配置文件中有些特定参数 扩展配置 max_connections connect_timeout interactive_timeout|wait_timeout net_retry_count ...
- Spring中基于注解方式管理bean
操作步骤 第一步:导入相关jar包 spring IoC的基本包 Spring支持注解的Jar包 第二步:创建Spring配置文件,ApplicationContext.xml 引入约束和开启注解扫描 ...