QOJ #12313. Three Indices 题解

news/2025/10/19 17:13:50/文章来源:https://www.cnblogs.com/Scarab/p/19151065

Description

一个字符串 \(t\) 被称为字符串 \(w\)平滑变换(smooth transformation),如果存在一个整数 \(m \ge 1\) 和一系列字符串
\(w_0, w_1, \ldots, w_m\),满足以下条件:

  • \(w_0 = w\),并且当 \(0 < i \le m\) 时,\(|w_i| = |w|\)
  • \(0 < i \le m\) 时,\(w_i\)\(w_{i-1}\) 至多在一个位置上不同;
  • \(t = w_0 w_1 \ldots w_m\)

现在给定一个字符串 \(s = s_1 s_2 \ldots s_{|s|}\),要求计算满足 \(1 \le i < j < k \le |s|\)\(s_{i..k} = s_i s_{i+1} \ldots s_k\)\(s_{i..j} = s_i s_{i+1} \ldots s_j\) 的平滑变换的三元组 \((i, j, k)\) 的数量。

\(n\leq 10^5\)

Solution

首先容易发现对于 \(|w_0|\) 不同的串,它们之间是没有多大的联系的,所以考虑对于 \(|w_0|\) 相同的一起做。

现在钦定 \(|w_i|\) 都是 \(L\),我们根据 NOI2016 优秀的拆分 的思路,对序列按照长度 \(L\) 分块,定义形如 \(kL\) 的点为关键点。

那么每个可能的 \(w_i\) 都一定恰好包含一个关键点,考虑枚举这个关键点。

\(w_i\) 的起点是 \(p\)\([p,p+L-1]\) 经过的关键点是 \(x\),同时定义:

  1. \(\text{LCP}(s_1,s_2)\)\(s_1,s_2\) 的最长公共前缀。
  2. \(\text{LCS}(s_1,s_2)\)\(s_1,s_2\) 的最长公共后缀。
  3. \(\text{LCP}'(s_1,s_2)\)\(s_1,s_2\) 至多有一个位置字符不同的最长公共前缀。
  4. \(\text{LCS}'(s_1,s_2)\)\(s_1,s_2\) 至多有一个位置字符不同的最长公共后缀。

然后分讨 \([p,p+L-1]\)\([p+L,p+2L-1]\) 不同的位置:

  1. 如果位置在 \([p,x]\) 之间,则形如

    此时需要满足 \(\text{LCS}'(s_{[1,x]},s_{1,x+L})\geq p-k+1,\text{LCP}(s_{[x+1,n]},s_{x+L+1,n})\geq p+L-1-x\)

  2. 如果位置在 \([x+1,p+L-1]\) 之间,形如

    需要满足 \(\text{LCS}(s_{[1,x]},s_{1,x+L})\geq p-k+1,\text{LCP}'(s_{[x+1,n]},s_{x+L+1,n})\geq p+L-1-x\)

容易发现对于相同的 \(x\)\(p\) 的限制实际上是一样的,所以暴力枚举 \(x\),可以得到包含 \(x\) 的长度为 \(L\) 的区间 \([p,p+L-1]\) 中,能与 \([p+L,p+2L-1]\) 接上的 \(p\) 的范围。

由于所有 \(w_i\) 开头的位置\(\bmod L\) 相同,所以从后往前维护一个关于余数的线段树即可。

时间复杂度:\(O(n\log n)/O(n\log^2n)\),取决于求 \(\text{LCP}/\text{LCS}/\text{LCP}'/\text{LCS}'\) 是用哈希加二分还是后缀数组。

具体细节见代码。

Code

#include <bits/stdc++.h>#define int int64_tusing u64 = uint64_t;const int kMaxN = 1e5 + 5, kMod = 998244353;int n;
int hs[kMaxN], pw[kMaxN];
std::string str;struct SGT {int sum[kMaxN * 4], tagc[kMaxN * 4], taga[kMaxN * 4];void pushup(int x) { sum[x] = sum[x << 1] + sum[x << 1 | 1]; }void addtagc(int x, int l, int r, int v) { sum[x] = 1ll * v * (r - l + 1), tagc[x] = v, taga[x] = 0; }void addtaga(int x, int l, int r, int v) { sum[x] += 1ll * v * (r - l + 1), taga[x] += v; }void pushdown(int x, int l, int r) {int mid = (l + r) >> 1;if (tagc[x] != -1) addtagc(x << 1, l, mid, tagc[x]), addtagc(x << 1 | 1, mid + 1, r, tagc[x]), tagc[x] = -1;if (taga[x]) addtaga(x << 1, l, mid, taga[x]), addtaga(x << 1 | 1, mid + 1, r, taga[x]), taga[x] = 0;}void build(int x, int l, int r) {sum[x] = taga[x] = 0, tagc[x] = -1;if (l == r) return;int mid = (l + r) >> 1;build(x << 1, l, mid), build(x << 1 | 1, mid + 1, r);}void updatec(int x, int l, int r, int ql, int qr, int v) {if (l > qr || r < ql) return;else if (l >= ql && r <= qr) return addtagc(x, l, r, v);pushdown(x, l, r);int mid = (l + r) >> 1;updatec(x << 1, l, mid, ql, qr, v), updatec(x << 1 | 1, mid + 1, r, ql, qr, v);pushup(x);}void updatea(int x, int l, int r, int ql, int qr, int v) {if (l > qr || r < ql) return;else if (l >= ql && r <= qr) return addtaga(x, l, r, v);pushdown(x, l, r);int mid = (l + r) >> 1;updatea(x << 1, l, mid, ql, qr, v), updatea(x << 1 | 1, mid + 1, r, ql, qr, v);pushup(x);}int query(int x, int l, int r, int ql, int qr) {if (l > qr || r < ql) return 0;else if (l >= ql && r <= qr) return sum[x];pushdown(x, l, r);int mid = (l + r) >> 1;return query(x << 1, l, mid, ql, qr) + query(x << 1 | 1, mid + 1, r, ql, qr);}
} sgt;void prework() {pw[0] = 1;for (int i = 1; i <= n; ++i) {pw[i] = 13331ll * pw[i - 1] % kMod;hs[i] = (13331ll * hs[i - 1] + str[i]) % kMod;}
}u64 gethash(int l, int r) { return (hs[r] - 1ll * hs[l - 1] * pw[r - l + 1] % kMod + kMod) % kMod; }int LCP(int x, int y) {if (x < 1 || y < 1 || x > n || y > n) return 0;int L = 0, R = n - std::max(x, y) + 2, res = 0;while (L + 1 < R) {int mid = (L + R) >> 1;if (gethash(x, x + mid - 1) == gethash(y, y + mid - 1)) L = res = mid;else R = mid;}return res;
}int _LCP(int x, int y) {if (x < 1 || y < 1 || x > n || y > n) return 0;int lim = n - std::max(x, y) + 1, lcp = LCP(x, y);if (std::max(x, y) + lcp + 1 <= n) return lcp + 1 + LCP(x + lcp + 1, y + lcp + 1);else return std::min(lcp + 1, lim);
}int LCS(int x, int y) {if (x < 1 || y < 1 || x > n || y > n) return 0;int L = 0, R = std::min(x, y) + 1, res = 0;while (L + 1 < R) {int mid = (L + R) >> 1;if (gethash(x - mid + 1, x) == gethash(y - mid + 1, y)) L = res = mid;else R = mid;}return res;
}int _LCS(int x, int y) {if (x < 1 || y < 1 || x > n || y > n) return 0;int lim = std::min(x, y), lcs = LCS(x, y);if (std::min(x, y) - lcs - 1 >= 1) return lcs + 1 + LCS(x - lcs - 1, y - lcs - 1);else return std::min(lcs + 1, lim);
}void dickdreamer() {std::cin >> str;n = str.size(), str = " " + str;prework();int ans = 0;sgt.build(1, 1, n);for (int L = 2; L <= n; ++L) {sgt.updatec(1, 1, n, 1, n, 0);for (int p = L * (n / L); p; p -= L) {if (p + L > n) {int l = 1, r = n - p + 1;ans += sgt.query(1, 1, n, l, r), sgt.updatea(1, 1, n, l, r, 1);} else {int l1 = p - _LCS(p, p + L) + 1, r1 = p + LCP(p + 1, p + L + 1) - L + 1;int l2 = p - LCS(p, p + L) + 1, r2 = p + _LCP(p + 1, p + L + 1) - L + 1;l1 -= p - L, r1 -= p - L, l2 -= p - L, r2 -= p - L;l1 = std::max<int>(l1, 1), r1 = std::min(r1, L);l2 = std::max<int>(l2, 1), r2 = std::min(r2, L);if (std::max(l1, l2) <= std::min(r1, r2)) {int l = std::min(l1, l2), r = std::max(r1, r2);ans += sgt.query(1, 1, n, l, r), sgt.updatea(1, 1, n, l, r, 1);sgt.updatec(1, 1, n, 1, l - 1, 1), sgt.updatec(1, 1, n, r + 1, L, 1);} else {if (l1 > l2) std::swap(l1, l2), std::swap(r1, r2);if (l1 <= r1 && l2 <= r2) {assert(r1 < l2);ans += sgt.query(1, 1, n, l1, r1), sgt.updatea(1, 1, n, l1, r1, 1);ans += sgt.query(1, 1, n, l2, r2), sgt.updatea(1, 1, n, l2, r2, 1);sgt.updatec(1, 1, n, 1, l1 - 1, 1), sgt.updatec(1, 1, n, r1 + 1, l2 - 1, 1), sgt.updatec(1, 1, n, r2 + 1, L, 1);} else if (l1 <= r1) {ans += sgt.query(1, 1, n, l1, r1), sgt.updatea(1, 1, n, l1, r1, 1);sgt.updatec(1, 1, n, 1, l1 - 1, 1), sgt.updatec(1, 1, n, r1 + 1, L, 1);} else if (l2 <= r2) {ans += sgt.query(1, 1, n, l2, r2), sgt.updatea(1, 1, n, l2, r2, 1);sgt.updatec(1, 1, n, 1, l2 - 1, 1), sgt.updatec(1, 1, n, r2 + 1, L, 1);} else {sgt.updatec(1, 1, n, 1, L, 1);}}}}}std::cout << ans << '\n';
}int32_t main() {
#ifdef ORZXKRfreopen("in.txt", "r", stdin);freopen("out.txt", "w", stdout);
#endifstd::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);int T = 1;// std::cin >> T;while (T--) dickdreamer();// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/940457.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

全面详解 C++std::vector用法指南

std::vector是 C++ 标准模板库(STL)中最重要、最常用的容器之一,它提供了​​动态数组​​功能,能够自动管理内存,支持快速随机访问,并在尾部高效添加/删除元素。 一、基础概念与特性 1.1 核心特性​​动态数组​…

022304105叶骋恺数据采集第一次作业

作业1 代码与运行结果 import urllib.request from bs4 import BeautifulSoupurl ="http://www.shanghairanking.cn/rankings/bcur/2020" response = urllib.request.urlopen(url, timeout=3) html= respons…

智能预加载:基于用户行为和路由预测

智能预加载:基于用户行为和路由预测 核心概念 智能预加载通过分析用户行为模式、路由关系和页面重要性,在用户实际访问前预先加载资源,显著提升用户体验。 实现架构 1. 行为数据收集层 class UserBehaviorTracker {…

函数简单传入参数的汇编分析 - 指南

函数简单传入参数的汇编分析 - 指南pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco…

2025年振动电机厂家推荐排行榜,新型/高频/防爆/低噪声/节能振动电机公司精选!

2025年振动电机厂家推荐排行榜,新型/高频/防爆/低噪声/节能振动电机公司精选!随着工业自动化和智能化的快速发展,振动电机作为许多机械设备中的关键部件,其性能和可靠性直接影响到整个系统的运行效率。为了帮助企业…

数据类型转换以及内存溢出

数据类型转换以及内存溢出数据类型转换以及内存溢出 public class Demo05 {public static void main(String[] args) {int i = 128;double b = i;//内存溢出//强制转换 (类型)变量名 高--低//自动转换 低--高…

2025年UV胶点胶机厂家推荐排行榜,全自动/智能/视觉定位/纽扣/拉链头/拉片/商标/钥匙扣/五金/徽章/线圈/硅胶点胶机公司推荐!

2025年UV胶点胶机厂家推荐排行榜,全自动/智能/视觉定位/纽扣/拉链头/拉片/商标/钥匙扣/五金/徽章/线圈/硅胶点胶机公司推荐!随着工业自动化技术的快速发展,UV胶点胶机在各个行业中的应用越来越广泛。从纽扣、拉链头…

25-deepin-linux-wsl-nginx-installation

windows11 #wsl #nginx 在 Deepin Linux 和 WSL 环境中安装配置 Nginx 静态资源服务器 概述 本文详细介绍了在 Deepin Linux 和 WSL (Windows Subsystem for Linux) 环境中安装 Nginx 并配置静态资源服务器的完整过程,…

美股数据接口对接指南:快速获取指数实时行情

美股数据接口对接指南:快速获取纳斯达克、道琼斯指数实时行情 在金融科技应用、量化交易或数据可视化项目中,接入可靠的美股市场数据是常见的需求。本文将详细介绍如何通过API接口,高效、稳定地获取包括纳斯达克综合…

2025国际冷链运输推荐腾翼搏时,专业温控保障生物药品安全!

2025国际冷链运输推荐腾翼搏时,专业温控保障生物药品安全!随着全球生物医药行业的快速发展,对冷链物流的需求日益增长。特别是在2025年,预计全球医药市场的规模将进一步扩大,生物药品、临床样本、CAR-T细胞治疗产…

鸿蒙设备开发-gpio控制

正在施工 说明 比harmony4.0的时候文档好太多了,基本每个文件夹(sdk组件)下都有对应的详细文档。 设备 用的RK35xx , 其实用什么设备都可以,都是kernel向上提供接口,只要可以运行kernel,性能可以基本都可以适配。…

QT肝8天01--工程介绍

QT肝8天01--工程介绍2025-10-19 16:57 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-f…

AI Agent和Agentic AI

https://hub.baai.ac.cn/view/46037本文来自博客园,作者:transformert,转载请注明原文链接:https://www.cnblogs.com/ac-network/p/19151066

升级Win11 25H2 专业工作站版 最强系统

全新 Windows 11 25H2 专业工作站版,是微软面向高性能用户与专业创作者推出的旗舰级系统版本。它不仅继承了 Windows 11 的全新设计与安全体系,更在底层性能优化、资源调度、文件系统、虚拟化支持等方面进行了深度增…

如何在Java中进行多线程编程

在Java中进行多线程编程有多种方式,最常用的包括继承Thread类、实现Runnable接口、实现Callable接口,以及使用线程池等。以下是具体介绍和代码示例: 1. 继承Thread类 通过继承Thread类并重写run()方法来定义线程执行…

Java中java.util.Random的用法

java.util.Random是Java中用于生成伪随机数的类,提供了多种生成不同类型随机数的方法。 基本用法 1. 创建Random对象 import java.util.Random;// 创建Random对象(使用默认种子,通常是系统时间) Random random = n…

我的学习开始及历程

学习嵌入式 Linux 驱动的历程回顾第一篇的随笔中我写到:「带来更多的体会和收获,就不会是毫无意义的普通文字了。」 所以就当作重新梳理自己来写这个随笔了。嵌入式的开始接触 大约是大一的时候,比较疯狂的「迷恋」…

2025年磨粉机厂家推荐排行榜,雷蒙磨粉机,环辊磨粉机,摆式磨粉机,矿石磨粉机,超微磨粉机,高压磨粉机公司推荐!

2025年磨粉机厂家推荐排行榜:雷蒙磨粉机、环辊磨粉机、摆式磨粉机、矿石磨粉机、超微磨粉机、高压磨粉机公司推荐!随着工业技术的不断进步,磨粉机在矿业、化工、建材等多个领域发挥着越来越重要的作用。为了帮助企业…

Java基础语法与面向对象

什么是重载(Overload)和重写(Override)?有什么区别?重载(Overload):同一类中,方法名相同但参数列表不同(参数类型、个数、顺序不同),与返回值无关。重写(Override):子类继承父类后,对父类的方法进行重…

从汇编角度看C++优化:编译器真正做了什么 - 教程

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …