后缀数组

本文详细介绍了SA算法的基本原理和实现步骤,包括如何通过倍增方法高效地确定字符串后缀的排名,以及如何利用基数排序和字符串的特性来优化计算过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

sa

终于学习了一波sa
sa的大概思想其实就是先把每一个i开头,长度为1的字符串排序,再通过前面的结果,用倍增的方法求出每一个i开头,长度为 2k 的字符串的排名
设sa[i]表示排名第i的后缀开头的位置,rank[i]为以i开头的后缀的排名,height[i]表示sa[i]和sa[i-1]的lcp(最长公共前缀)
假设现在我们在对以i开头,长度为k的字符串排序,那么我们是已经知道长度为k/2长度的字符串的排名了的,设x[i]表示[i..i+k-1]这个字符串在所有长度为k的字符串的排名,y[i]表示排名为i的字符串[j+k..j+k+k-1]的j
那么x数组是已知的,因为前面已经求过了,那么我们现在考虑一下求y数组
设s[0..len-1]
首先j的位置在[len-k..len-1]这一段的对应的字符串是空的,那么它们肯定是最小的
其他位置对应的名次我们可以通过sa数组来求
如果有一个sa[i]的值是>=k的,那么我们就有一个j对应sa[i]所表示的那个字符串了,比如sa[i]=3,k=2,那么j=1就等价于sa[i]所表示的字符串
观察一下sa数组的定义,发现sa已经把全部的k/2长度的字符串排序了,那么我们只要从小到大枚举sa[i]中的i,所得到的一系列的j对应的字符串其实也是有序的,那么只要将他们依次放入y数组里面就好了
现在我们考虑一下使用基数排序通过当前的排序把长度为k的字符串进行排序
考虑一下这个问题的本质,如果要比较i,j的排名,就是先比较x的大小,如果x相同再比较在y中的排名
先把每一个x[i]放入一个名为buc的桶里面(其实为什么网上面的程序打的都是什么x[y[i]]搞到懵逼了那么久虽然是我太菜了),那么我们就相当于已经把x[i]不同的分开了,假设某一个x[i]一共有p个,那么它们的排名就是[t+1..t+p],现在要将他们排序,就可以从后到前枚举每一个x[y[i]]的i,那么我们先枚举到的就是排名最大的t+p名,然后每一个依次减小,这样就可以快速的进行排序了
最后为了节省空间,我们还要重新给每一个新的x[i](即长度为k对应的x)进行一个类似压缩的操作,如果sa[i]对应的字符串等于sa[i-1]对应的字符串,那么就有x[sa[i]]=x[sa[i-1]],否则x[sa[i]]=x[sa[i-1]]+1,最后如果x[sa[len]](sa从1开始排名)已经等于len了那么所有的后缀都已经排好序了,可以直接退出

sa代码

void gesa(){
    len=strlen(s);
    memset(y,0,sizeof(y));
    m=199;
    fo(i,1,len) buc[i]=0;
    fo(i,0,len-1) x[i]=s[i];
    fo(i,0,len-1) buc[x[i]]++;
    fo(i,1,len) buc[i]+=buc[i-1];
    fo(i,0,len-1) sa[buc[x[i]]--]=i;
    for(k=1;k<=len;k=k*2){
        p=0;
        fo(i,len-k,len-1) y[p++]=i;
        fo(i,1,len) if (sa[i]>=k) y[p++]=sa[i]-k;
        fo(i,1,m) buc[i]=0;
        fo(i,0,len-1) buc[x[i]]++;
        fo(i,1,m) buc[i]+=buc[i-1];
        for(i=len-1;i>=0;i--) sa[buc[x[y[i]]]--]=y[i];
        fo(i,0,len-1) y[i]=x[i];
        p=1; x[sa[1]]=1;
        fo(i,2,len){
            if (y[sa[i]]==y[sa[i-1]] && y[sa[i]+k]==y[sa[i-1]+k]) x[sa[i]]=p;
            else x[sa[i]]=++p;
        }
        m=p;
        if (m>=len) break;
    }
    fo(i,1,len) rank[sa[i]]=i;
}

height

height的定义是sa[i-1]和sa[i]开头的后缀的lcp,求这个东西似乎要 n2 ,但是因为一个性质,可以把这个东西优化成O(n)级别的
设k=sa[rank[i]-1],即排名为i开头的后缀的排名-1的后缀,现在我们已经知道他们的lcp为height[rank[i]],现在我们要求height[rank[i+1]],那么有height[rank[i+1]]>=height[rank[i]]-1
为什么呢?觉得网上的证明十分的难懂,然后就自己举了一个例子模拟了一下
现在要求字符串ababa的height,假设现在已经知道了height[rank[1]] (这个东西等于3)现在我们要求height[rank[2]],那么我们发现height[rank[1]]表示的是有一个后缀的前三位与从第一位开始的后缀的后缀的前三位相同,那么就是说一定有一个后缀与从第二位开始的后缀有2位相同
抽象一点的说法:现在已经知道了i开头的字符串和k开头的字符串的lcp=t,那么起码有一个以k+1开头的字符串和以i+1开头的字符串的lcp=t-1
在有这个条件的情况下,我们就可以用很少的时间依次求出height[rank[i]]了

贴代码

#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<cstdio>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;

const int maxn=100005;

int sa[maxn],rank[maxn],height[maxn],x[maxn],y[maxn],buc[maxn];
int i,j,k,l,n,m,len,p,t1,t2;
char s[maxn];

void gesa(){
    len=strlen(s);
    memset(y,0,sizeof(y));//注意这句话,在有多组数据时要加
    m=199;
    fo(i,1,len) buc[i]=0;
    fo(i,0,len-1) x[i]=s[i];
    fo(i,0,len-1) buc[x[i]]++;
    fo(i,1,len) buc[i]+=buc[i-1];
    fo(i,0,len-1) sa[buc[x[i]]--]=i;
    for(k=1;k<len;k=k*2){
        p=0;
        fo(i,len-k,len-1) y[p++]=i;
        fo(i,1,len) if (sa[i]>=k) y[p++]=sa[i]-k;
        fo(i,1,m) buc[i]=0;
        fo(i,0,len-1) buc[x[i]]++;
        fo(i,1,m) buc[i]+=buc[i-1];
        for(i=len-1;i>=0;i--) sa[buc[x[y[i]]]--]=y[i];
        fo(i,0,len-1) y[i]=x[i];
        p=1; x[sa[1]]=1;
        fo(i,2,len){
            if (y[sa[i]]==y[sa[i-1]] && y[sa[i]+k]==y[sa[i-1]+k]) x[sa[i]]=p;
            else x[sa[i]]=++p;
        }
        m=p;
        if (m>=len) break;
    }
    fo(i,1,len) rank[sa[i]]=i;
}
void gehei(){
    k=0;
    fo(i,0,len-1){
        if (rank[i]==1){
            height[rank[i]]=0; continue;
        }
        if (k) k--;
        t1=i+k; t2=sa[rank[i]-1]+k;
        while (t1<len && t2<len){
            if (s[t1]==s[t2]) k++; else break;
            t1++; t2++;
        }
        height[rank[i]]=k;
    }
}
int main(){
    //freopen("s.in","r",stdin);
    //freopen("s.out","w",stdout);
    scanf("%s",s);
    gesa();
    fo(i,1,len) printf("%d ",sa[i]+1);
    gehei();
    printf("\n");
    fo(i,2,len) printf("%d ",height[i]);
}

一些题目

两个字符串的最长公共子串

### 原理 模拟退火算法(Simulated Annealing, SA)是一种基于概率的启发式优化方法,其核心原理来源于固体物理中的退火过程。在金属材料加热至高温后缓慢冷却时,原子会逐渐形成低能量、稳定的晶体结构。SA算法借鉴这一现象,通过引入一个温度参数 $ T $ 来控制搜索过程中对解空间的探索强度。 - **Metropolis接受准则**:该准则是SA的核心机制之一,它决定了是否接受一个新解。如果新解优于当前解,则一定接受;若劣于当前解,则以一定的概率接受,该概率由下式计算: $$ P(\Delta E) = \exp\left(-\frac{\Delta E}{T}\right) $$ 其中 $ \Delta E $ 是新旧解之间的目标函数差值,$ T $ 是当前温度[^3]。 - **降温策略**:温度随迭代逐步下降,常见的降温方式包括指数降温 $ T_{k+1} = \alpha T_k $(其中 $ 0 < \alpha < 1 $)、线性降温等。温度越高,算法越容易接受较差的解,从而避免陷入局部最优;随着温度降低,算法趋于稳定,收敛到较优解附近[^2]。 ### 实现 SA算法的基本实现流程如下: 1. 初始化:设定初始温度 $ T_0 $、降温系数 $ \alpha $、终止温度 $ T_{min} $,并随机生成一个初始解 $ x_{current} $。 2. 在当前温度下,根据当前解生成一个邻域解 $ x_{new} $。 3. 计算新解与当前解的目标函数差 $ \Delta E = f(x_{new}) - f(x_{current}) $。 4. 如果 $ \Delta E < 0 $,则接受新解;否则以概率 $ \exp(-\Delta E / T) $ 接受新解。 5. 更新当前解,并重复步骤2~4若干次(通常为每个温度下的迭代次数)。 6. 按照降温策略降低温度 $ T $,直到满足终止条件(如温度低于 $ T_{min} $)。 7. 输出当前最优解。 以下是一个简单的Python实现示例,用于求解一维函数最小化问题: ```python import math import random def objective_function(x): return x**2 # 示例目标函数:f(x) = x^2 def simulated_annealing(objective, initial_sol, temp_initial, alpha, temp_min): current_sol = initial_sol current_energy = objective(current_sol) best_sol = current_sol best_energy = current_energy temperature = temp_initial while temperature > temp_min: # 生成邻域解 new_sol = current_sol + random.uniform(-1, 1) new_energy = objective(new_sol) delta_energy = new_energy - current_energy # 判断是否接受新解 if delta_energy < 0 or random.random() < math.exp(-delta_energy / temperature): current_sol = new_sol current_energy = new_energy # 更新最优解 if current_energy < best_energy: best_sol = current_sol best_energy = current_energy # 降温 temperature *= alpha return best_sol, best_energy # 参数设置 initial_solution = random.uniform(-10, 10) initial_temperature = 1000 cooling_rate = 0.99 minimum_temperature = 1e-3 best_x, best_f = simulated_annealing(objective_function, initial_solution, initial_temperature, cooling_rate, minimum_temperature) print(f"最优解: x = {best_x}, 最小值: f(x) = {best_f}") ``` ### 应用场景 SA算法因其全局搜索能力和较强的鲁棒性,广泛应用于多个领域,主要包括: - **组合优化问题**:例如旅行商问题(TSP)、车间调度问题(JSP)等,这些问题的解空间复杂且存在大量局部极值点。 - **工程设计**:如电路布局优化、结构设计、路径规划等需要多变量协同调整的问题。 - **机器学习模型参数调优**:作为超参数搜索的一种替代手段,在支持向量机(SVM)、神经网络等模型中均有应用。 - **金融投资组合优化**:用于在风险与收益之间寻找平衡点,构建最优资产配置方案[^1]。 此外,SA还被用于图像处理、信号恢复、数据聚类等领域。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值