首页 > 其他分享 >Border 理论学习笔记

Border 理论学习笔记

时间:2025-01-10 20:47:10浏览次数:1  
标签:pre 匹配 log Border 笔记 st 学习 border 周期

Border 理论学习笔记

约定

  • 字符串下标从 \(1\) 开始。

  • 定义 \(S+T\) 为 \(S\) 与 \(T\) 这两个字符串的拼接。

  • 定义 \(S[l:r]=S_l+S_{l+1}+\cdots +S_r\)。

  • 定义 \(pre(i,S)=S[i:n],suf(i,S)=S[|S|-i+1:n]\),也就是 \(S\)​ 的前后缀。

  • 对于 \(S,T\),若 \(T[i:i+|S|-1]\),则称 \(i\) 是 \(S\) 在 \(T\) 中的匹配位置。

周期与 border

定义

若存在 \(p\),使得 \(\forall 1\le i\le |S|-p,s_i=s_{i+p}\),我们称 \(p\) 为 \(S\) 的周期。注意这里的周期并不一定能整除 \(|S|\)。例如,对于 \(\text{abbabbab}\),我们也称 \(3\) 为其周期。

称 \(per(S)\) 为 \(S\) 的最小周期。

若存在 \(r\),使得 \(pre(i,S)=suf(i,S)\),则称 \(pre(i,S)\) 为 \(S\) 的 border。

也就是说,周期是一个长度,border 是一个字符串。

性质

  • $pre(i,S) $ 是 \(S\) 的 border $\Leftrightarrow $ \(|S|-i\) 为 \(S\) 的周期。

证明:\(pre(i,S)=suf(i,S)\),即 \(S[1:i]=S[|S|-i+1:n]\),即 \(S_j=S_{j+|S|-i}\)。由周期定义得到 \(|S|-i\) 为 \(S\) 的周期。反过来也一样。

  • Weak Periodicity Lemma:对于字符串 \(S\),若 \(p,q\) 为 \(S\) 的周期,\(p+q\le |S|\),那么 \(\gcd(p,q)\) 为 \(S\) 的周期。

证明:不妨设 \(p>q\)。

根据周期定义:

\(\forall i\le q,S_i=S_{i+p}=S_{i+p-q}\)。

\(\forall i>q,S_i=S_{i-q}=S_{i+p-q}\)。

因此,\(\forall 1\le i\le |S|-(p-q),S_i=S_{i+p-q}\)。因此 \(p-q\) 为 \(S\) 的周期。

根据更相减损法的过程,显然 \(\gcd(p,q)\) 也为 \(S\) 的周期。

  • Periodicity Lemma:对于字符串 \(S\),若 \(p,q\) 为 \(S\) 的周期,\(p+q\le |S|+\gcd(p,q)\),那么 \(\gcd(p,q)\) 为 \(S\) 的周期。

不会证捏。

  • 对于字符串 \(S,T\) ,若 \(2|S|\ge |T|\),则 \(S\) 在 \(T\) 中的所有匹配位置构成等差序列。

证明:在匹配位置个数小于 \(3\) 时显然成立。

在匹配位置个数大于等于 \(3\) 时,我们考虑第一个,第二个和另外任意一个匹配串:

图中红色部分均为 \(P_1\) 的 border。可以得出蓝色部分是 \(P_1\) 的一个周期。

我们设 \(P_1,P_2\) 间隔为 \(d\),\(P_2,P_t\) 间隔为 \(f\)。

那么 \(r=\gcd(d,f)\) 为 \(P_1\) 的周期。我们设 \(per(P_1)=p\le r\)。

假设橙色部分为 \(pre(|S|-p,S)\),若 \(p<d\),发现从第二条橙色位置(即 \(P_1\) 右移 \(p\) 位)开始又能形成一个匹配。可以结合图像理解,因为 \(P_2\) 的前缀与 \(P_1\) 相同,第二三条橙色拼到一起恰好能形成匹配。

那么,因为 \(P_2\) 是第二个匹配位置,这产生了矛盾。因此,\(p=d\),\(r\ge d\) 且 \(r=\gcd(d,f)\le d\),得 \(r=d\)。那么 \(d|f\),也就是说,对于任何 \(t\ge 3\),\(P_2,P_t\) 的间隔是 $d $ 的倍数。同时,类似于上一段的分析,间隔 $d $ 的倍数处又能形成匹配,因此 \(S\) 在 \(T\) 中的所有匹配位置构成等差序列。

border 的结构

  • 若 \(pre(i,S)\) 是 \(S\) 的 border,\(pre(j,S)(j<i)\) 是 \(pre(i,S)\) 的 border。

border 的 border 是 border。比较显然,就不证了。

  • \(S\) 的长度不小于 \(\dfrac{|S|}{2}\) 的 border 长度构成一个等差序列。

证明:设 \(n-p\) 为 \(S\) 的最长 border,\(n-q\) 也为 \(S\) 的 border。(\(p,q\ge \frac{|S|}{2}\))

那么 \(p,q\) 为 \(S\) 的周期,由 Weak Periodicity Lemma 有 \(\gcd(p,q)\) 为 \(S\) 的周期。

由于 \(n-p\) 是最长 border,\(p\le \gcd(p,q)\),得 \(p=\gcd(p,q)\) 即 \(p|q\)。

有了整除就和上上个性质很像了。通过与上面类似的分析可以得出该结论。

  • 推论:\(S\) 的所有 border 长度可以组成 \(O(\log |S|)\) 个等差数列。

证明:将 \(S\) 所有 border 按照长度分为 \([1,2),[2,4),[4,8)\cdots [2^{i-1},2^i),[2^i,S)\) 这 \(O(\log |S|)\) 类。

我们以每一组中的最长 border 作为基准,那么根据上面的定理,这一组中所有 border 的长度构成等差序列。那么总共就是 \(O(\log |S|)\) 个等差序列。

子串周期查询

问题:一个长度为 \(n\) 的字符串 \(S\),每次询问一个子串 \(T\) 的所有周期,用 \(O(\log |T|)\) 个等差序列表示。

问周期等价于问 border。

我们首先将 border 按照长度分类:

  1. \(x\in[2^i,2^{i+1})\)。
  2. \(x\in[2^k,|T|)\)。

首先,我们先解决第一种情况。

如图,对于 border \(u\),\(pre(2^i,T)\) 是 \(u\) 的前缀,\(suf(2^i,T)\) 是 \(u\) 的后缀。

因此,我们找出所有 \(suf(2^i,T)\) 在 \(pre(2^{i+1},T)\) 中的匹配位置(这里的匹配位置指的是 \(suf(2^i,T)\) 的串首字符对应位置)和所有 \(pre(2^i,T)\) 在 \(suf(2^{i+1},T)\) 中的匹配位置(这里的匹配位置指的是 \(pre(2^i,T)\) 的串末字符对应位置),并计算两边匹配位置到 \(T\) 两端的长度(图中红色位置),发现如果\(u\) 是 \(T\) 的 border,那么两端红色位置的长度是相同的。

那么,我们求出所有 \(suf(2^i,T)\) 在 \(pre(2^{i+1},T)\) 中的匹配位置和所有 \(pre(2^i,T)\) 在 \(suf(2^{i+1},T)\) 中的匹配位置,再翻折,合并就能得到这一段的答案。

考虑情况二,发现本质相同,就是将上面的 \(2^i\gets 2^k,2^{i+1}\gets |T|\)。

因此,我们现在要解决的问题形如:

  1. 求出 \(suf/pre(2^i,S)\) 在 \(pre(len,S)\) 中的匹配位置。
  2. 合并两个等差数列。

我们先考虑问题 1。发现我们要匹配的字符串长度形如 \(2^i\)。考虑倍增 SA 的过程,如果我们在过程中将字符串的 \(rk\) 记录下来,那么对于所有长度为 \(2^i\) 的字符串,它们在第 \(i\) 轮的 \(rk\) 是相同的。我们按照下标顺序,将每一轮中 \(rk\) 相同的字符串的下标插入 vector 中,那么查询某个区间中的匹配位置直接二分查找即可。具体地,由于匹配位置是一个等差序列,我们只需查找第一二个和最后一个匹配位置即可。复杂度是 \(O(\log^2 n)\) 的,因为我们要做 \(O(\log n)\) 轮二分。

问题二中合并两个等差序列是好解决的,可以通过一些解同余方程之类的东西做到 \(O(\log^2 n)\)。

到这里,我们就能用 \(O(n\log n)-O(\log^2n)\) 的复杂度解决这个问题了。

能不能更给力?发现瓶颈就是上面的两个问题,考虑优化。

首先对第一个问题进行优化。实际上,我们只在乎形如 \([x,x+2^i-1]\) 的段的匹配信息。这样的段一共只有 \(O(n)\) 个,考虑通过一些手段快速求出这些段的匹配信息。

发现长为 \(2^i\) 的字符串只有 \(O(n)\) 个,而 \(i\) 的取值只有 \(O(\log n)\) 种,那么匹配信息的空间复杂度是 \(O(n\log n)\) 的,可以用哈希表全部存下。具体地,对于这 \(O(n)\) 个段,我们都开一个哈希表来维护信息,对每一个哈希值,我们维护该值对应字符串在区间中第一次/第二次/最后一次出现的位置即可 \(O(1)\) 查询。哈希值 \(h_{i,i+2^j-1}\) 可以通过 \(h_{i,i+2^{j-1}-1}\) 和 \(h_{i+2^{j-1},i+2^j-1}\) \(O(1)\) 合并,因此预处理的复杂度是 \(O(n\log n)\) 的。

上面的方法要求匹配区间的长度也是 \(2^i\),可能在做 \([2^k,|T|)\) 的时候不适用,这一段要暴力求。这样,我们就将第一部分的单次询问复杂度优化到了 \(O(\log n)\)。

然后考虑第二个问题。事实上,我们有下面的性质:

  • 对于四个字符串满足 \(|x_1|=|y_1|\ge |x_2|=|y_2|\),\(x_1\) 在 \(y_2+y_1\) 中至少匹配 \(3\) 次,\(y_1\) 在 \(x_1+x_2\) 中至少匹配 \(3\) 次,那么 \(x_1\) 与 \(y_1\) 最小周期相等。

证明:若上述性质不成立,不妨设 \(per(x_1)>per(y_1)\),考虑 \(x_1\) 在 \(y_1+y_2\) 中的最后一次匹配,设其与 \(y_1\) 的重叠部分为 \(z\),则 \(|z|>2per(x_1)>per(x_1)+per(y_1)\),则 \(z\) 有周期 \(d=\gcd(per(x_1),per(y_1))\),那么 \(d|per(x_1)\) 且 \(d<per(x_1)\),那么 \(d\) 为 \(x_1\) 周期且 \(d<per(x_1)\),矛盾。

于是,我们的两个等差序列要么其中一个项数小于 \(3\),要么公差相同,显然可以 \(O(1)\) 合并。

至此,我们以 \(O(n\log n)-O(\log n)\) 的复杂度解决了这个问题。

有一道题 [BJWC2018] Border 的四种求法 是求最长 border,可以用来测试上面的问题。

给出 \(O(\log^2n)\) 的实现:

#include<bits/stdc++.h>
using namespace std;
char s[1000010];
int Q,l,r,n,m,cnt;
int ans,bu[1000010],k1[20][200010],p[1000010],sa[1000010];
vector<int> pos[20][200010];
struct Seq{
	int st,ed,sum,delta;
}none;
bool In(Seq x,int y){
	return !((y-x.st)%x.delta);
}
Seq Merge(Seq x,Seq y){
	if(x.delta==y.delta){
		if(x.st>y.st) swap(x,y);
		if(x.ed<y.st) return {0,0,0,0};
		x.st=y.st;x.ed=min(x.ed,y.ed);x.sum=(x.ed-x.st)/x.delta+1;
		return x;
	}
	if(x.sum<y.sum) swap(x,y);
	int cnt=0,a[10]={},z=y.st;
	for(int i=1;i<=y.sum;i++){
		if(In(x,z)) a[++cnt]=z;
		z+=y.delta;
	}
	if(!cnt) return {0,0,0,0};
	x.st=a[1],x.ed=a[cnt],x.sum=cnt;
	if(cnt>1) x.delta=(x.ed-x.st)/(cnt-1);
	else x.delta=0;return x;
}
int fndl(int a,int b,int x){
	int l=0,r=pos[a][b].size()-1;
	if(r<0||pos[a][b][r]<x) return -1;
	while(l<r){
		int mid=(l+r)>>1;
		if(pos[a][b][mid]<x) l=mid+1;
		else r=mid;
	}
	return l;
}
int fndr(int a,int b,int x){
	int l=0,r=pos[a][b].size()-1;
	if(r<0||pos[a][b][0]>x) return -1;
	while(l<r){
		int mid=(l+r+1)>>1;
		if(pos[a][b][mid]<=x) l=mid;
		else r=mid-1;
	}
	return l;
}
int workans(int k,int l1,int r1,int l2,int r2){
	int L=(1<<k),ps1=k1[k][l1],ps2=k1[k][r2-L+1];
	int f1=fndl(k,ps1,l2);
	int f3=fndl(k,ps2,l1);
	if(f1==-1||f3==-1) return 0;
	int f2=fndr(k,ps1,r2-L+1);
	int f4=fndr(k,ps2,r1+1-L);
	if(f2==-1||f4==-1||f1>f2||f3>f4) return 0;
	Seq x,y;
	if(f1==f2) x.st=x.ed=pos[k][ps1][f1],x.delta=1,x.sum=1;
	else{
		x.st=pos[k][ps1][f1],x.ed=pos[k][ps1][f2],
		x.delta=pos[k][ps1][f1+1]-x.st,x.sum=(x.ed-x.st)/x.delta+1;
	}
	if(f3==f4) y.st=y.ed=pos[k][ps2][f3],y.delta=1,y.sum=1;
	else{
		y.st=pos[k][ps2][f3],y.ed=pos[k][ps2][f4],
		y.delta=pos[k][ps2][f3+1]-y.st,y.sum=(y.ed-y.st)/y.delta+1;
	}
	Seq z=y;y.st=(l1+r2-z.ed-L+1),y.ed=(l1+r2-z.st-L+1);
	x=Merge(x,y);
	if(x.sum) return r2-x.st+1;
	return 0;
}
int main()
{
	scanf("%s",s+1);
	n=strlen(s+1),m=26;
	for(int i=1;i<=n;i++) ++bu[s[i]-'a'+1],k1[0][i]=s[i]-'a'+1;
	for(int i=1;i<=m;i++) bu[i]+=bu[i-1];
	for(int i=1;i<=n;i++) sa[bu[s[i]-'a'+1]]=i,--bu[s[i]-'a'+1];
	for(int i=1;i<=n;i++) pos[0][k1[0][i]].push_back(i);
	for(int k=1;k<=n;k<<=1){
		int lgk=__lg(k)+1;
		cnt=0;
		for(int i=n-k+1;i<=n;i++) p[++cnt]=i;
		for(int i=1;i<=n;i++){
			if(sa[i]>k) p[++cnt]=sa[i]-k;
		} 
		memset(bu,0,sizeof(bu));
		for(int i=1;i<=n;i++) ++bu[k1[lgk-1][i]];
		for(int i=1;i<=m;i++) bu[i]+=bu[i-1];
		for(int i=n;i>=1;i--) sa[bu[k1[lgk-1][p[i]]]]=p[i],--bu[k1[lgk-1][p[i]]];
		k1[lgk][sa[1]]=1,m=1;
		for(int i=2;i<=n;i++){
			if(k1[lgk-1][sa[i]]==k1[lgk-1][sa[i-1]]&&k1[lgk-1][sa[i]+k]==k1[lgk-1][sa[i-1]+k]) k1[lgk][sa[i]]=m;
			else k1[lgk][sa[i]]=++m;
		} 
		for(int i=1;i<=n;i++) pos[lgk][k1[lgk][i]].push_back(i);
	} 
	scanf("%d",&Q);
	for(int i=1;i<=Q;++i){
		scanf("%d%d",&l,&r);ans=0;
		for(int k=__lg(r-l)+1;k;--k){
			ans=workans(k-1,l,min(l+(1<<k)-1,r-1),max(l+1,r-(1<<k)+1),r);
			if(ans){
				printf("%d\n",ans);
				break;
			}
		}
		if(ans==0) printf("%d\n",ans); 
	}
	return 0;
}

Reference

标签:pre,匹配,log,Border,笔记,st,学习,border,周期
From: https://www.cnblogs.com/liulianll/p/18664648

相关文章

  • Qt C++学习笔记1.7
    1.7Qt入门:实现一个图片查看软件需要用到的控件:QLabelQLineEditQPushButton需要实现的功能:打开目录选择图片显示图片的名字显示图片QLabel基本用法设置文本voidsetText(constQString&);获取文本QStringtext()const;设置图片voidsetPixmap(constQPixm......
  • 1.9日学习笔记之高阻态和开漏输出
    三态:高电平、低电平和高阻态高阻态输出(High-ZOutput):高阻态输出是指一个IO口处于高阻抗状态,此时IO口既不输出高电平也不输出低电平,而是呈现高阻抗状态,相当于断开电路。高阻态输出的主要用途是:多设备共享总线:允许多个设备共享同一根数据线,但每次只有一个设备能够控制这条数据......
  • JavaScript ES2023/2024 新特性学习总结
    JavaScriptES2023/2024新特性学习总结ES2023/2024规范新特性与最佳实践总结作者:在人间耕耘更新时间:2025年1月10日目录前言核心特性概览ES2023新特性实战ES2024新特性实战实际开发应用场景性能与最佳实践总结前言ES2023/2024规范引入多项新特性,本文......
  • 农业4.0背后的智慧引擎:机器学习助力精准农事决策
    农业4.0背后的智慧引擎:机器学习助力精准农事决策在21世纪的科技浪潮中,农业作为人类生存和发展的基石,正经历着前所未有的变革。从传统的农耕文明到现代化的机械农业,再到如今智能化的农业4.0时代,每一步都凝聚着科技的力量。而在这场变革中,机器学习作为人工智能的重要分支,正逐......
  • 05、Docker学习,常用安装:Mysql、Redis、Nginx、Nacos
    Docker学习,常用安装:Mysql、Redis、Nginx、Nacos一、Docker安装Mysql1、dockersearchmysql ##查找mysql版本都有哪些2、dockerpullmysql:5.6 ##下载5.6版本的mysql镜像3、dockerrun-p13306:3306--namemysql ##运行镜像生成容器-v/opt......
  • 深度学习笔记11-优化器对比实验(Tensorflow)
    ......
  • 【学习资源】MBSE和工业软件
    工业软件从业者,需要学习与应用MBSE方法论,解决复杂问题的有效手段。笔者做一个简单介绍。1什么是MBSE?MBSE(Model-BasedSystemsEngineering,基于模型的系统工程)是一种系统工程方法论,其利用模型作为系统设计、分析、验证和验证的主要手段。MBSE用模型来记录系统需求、设计、分......
  • C/C++ 数据结构与算法【排序】 常见7大排序详细解析【日常学习,考研必备】带图+详细代
    常见7种排序算法冒泡排序(BubbleSort)选择排序(SelectionSort)插入排序(InsertionSort)希尔排序(ShellSort)归并排序(MergeSort)快速排序(QuickSort)堆排序(HeapSort)计数排序(CountingSort)算法复杂度1、冒泡排序冒泡排序是一种简单的排序算法,它重复地遍历要排序的数列,一次比......
  • 基于扩展DDPG算法的无人机辅助无 线供电物联网网络多目标优化——学习笔记
    Ⅰ、论文笔记一、研究背景与相关工作(一)研究背景物联网技术发展促使设备数量剧增,对通信系统的数据速率和覆盖率要求提升,且设备能量供应面临挑战。5G、6G及相关技术如WPT为解决这些问题提供了支撑,无人机在无线网络中的应用也日益受到关注,其与WPT结合成为物联网网络关......
  • 《程序员修炼之道:从小工到专家》读书笔记(八)
    这篇读书笔记主要为第七章“在项目开始之前”的内容。这一章通过五个小节,详细阐述了在项目正式开始之前,程序员和团队需要面对和解决的一系列关键问题。“需求之坑”这一节让我意识到,需求的不明确或频繁变动是软件开发中常见的问题。它提醒我们,在项目启动之初,就必须投入足够的时间......