字符串乱记

就是一些关于字符串的笔记,没什么别的。

毫无顺序的目录
  • 最小表示法
  • Manacher
  • trie

最小表示法#

定义:#

首先要了解一个东西叫循环同构。

形式化定义为:

当字符串 S 中可以选定一个位置 i 满足

S[i...n]+S[1...i1]=T

则称 ST 循环同构

举一个通俗易懂的例子:

例如有一个字符串:absdc

它的循环同构有 bsdcasdcabdcabscabsd

至于最小表示就是它与它所有循环同构中字典序最小的字符串。

做法#

考虑 O(n) 时间复杂度的做法。

首先为了防止越界的分类讨论,可以将字符串进行倍长。

然后设置两个指针,表示最小循环的起始位置,暴力求出最长公共前缀,即 lcp(i,j) 设其为 k

接着比较 si+ksj+k

假设 si+k<sj+k ,则显然 jj+k 均不能成为起始位置,直接令 jj+k+1 后继续比较即可。

点击查看代码
int l = 1 , r = 2;
while(l <= n && r <= n)
{
    int k = 0;
    while(k <= n && s[l + k] == s[r + k])
        ++k;
    if(s[l + k] < s[r + k])
        r += k + 1;
    else l += k + 1;
    r += (r == l);
}

一道例题#

P5211 [ZJOI2017]字符串

这确实是一道很好的例题,可惜太弱了,不会做。

以后做了,再补吧。

Manacher#

算法步骤#

一个短小,精悍还贼快的一个算法。

重点在于理解其实也不难理解

首先考虑对于原字符串进行一些改变。

我们发现,对于一个回文字符串而言,它有两种形式:

abbaaba

即中心轴是一个点或者一个空位,我们考虑在每一个字符中间加一个板子(就是随意一个原字符串不会出现的字符),就可以避免分类讨论了。

下文中的操作,都是在新字符串上进行操作的。

leni 表示以位置 i 为中心的最长奇回文串半径,考虑从左往右依次求 leni

记录前缀中回文串右端点的最大值 mx 及其对应的回文中心 pos

由于回文的对称性我们发现,如果当前扫到的节点在此时记录的最大的回文串内。

那么在回文串的另一边的回文半径就与此时节点一样的。

但是,回文串的对称性同样只在回文串的范围内,因此还要特判一下边界。

至于如何求对称点,小学奥数的中点公式: l+r=mid2l=mid2r

即:

lenjmin(len2×posj,mxj)

继承完了之前的直接暴力继续枚举更新即可。

显然 mx 是单调递增的,故时间复杂度 O(n)

代码很好写
for(int i = 1 , mid = 0 , r = 0;i <= n;i++)
{
    l[i] = (i < r ? min(l[mid * 2 - i] , r - i) : 1);
    while(s[i + l[i]] == s[i - l[i]]) l[i]++;
    if(i + l[i] > r) r = i + l[i] - 1 , mid = i;
    ans = max(ans , l[i]);
}

两道例题#

P5446 [THUPC2018]绿绿和串串

solution

合法的串 S 一定是 T 的前缀。先用 Manacher 求出每个位置的回文半径,从后往前处理,一个位置合法当且仅当其回文半径右侧达到 |T|,或者左侧达到 1 且右侧位置合法。

代码也很好写
#include <bits/stdc++.h>
using namespace std;

const int N = 2000010;

int t , n , l[N] , vis[N];
char s[N] , s1[N];

inline int read()
{
    int asd = 0 , qwe = 1; char zxc;
    while(!isdigit(zxc = getchar())) if(zxc == '-') qwe = -1;
    while(isdigit(zxc)) asd = asd * 10 + zxc - '0' , zxc = getchar();
    return asd * qwe;
}

int main()
{
    t = read();
    while(t--)
    {

        scanf("%s" , s1 + 1) , n = strlen(s1 + 1);
        s[0] = '~' , s[1] = '*';
        for(int i = 1;i <= n;i++) s[i * 2] = s1[i] , s[i * 2 + 1] = '*';
        n = n * 2 + 1 , s[n + 1] = '@';
        for(int i = 1 , mid = 0 , r = 0;i <= n;i++)
        {
            l[i] = (i <= r ? min(l[mid * 2 - i] , r - i + 1) : 1);
            while(s[i + l[i]] == s[i - l[i]]) l[i]++;
            if(i + l[i] > r) r = i + l[i] - 1 , mid = i;
        }
        for(int i = n;i >= 1;i--)
        {
            if(i + l[i] - 1 == n) vis[i] = 1;
            else if(i - l[i] + 1 == 1 && vis[i + l[i] - 2]) vis[i] = 1;
        }
        for(int i = 1;i <= n;i++)
        {
            if(vis[i] && s[i] >= 'a' && s[i] <= 'z') printf("%d " , i / 2);
            vis[i] = 0 , s[i] = ' ';
        }
        puts("");
    }
    return 0;
}

P4555 [国家集训队]最长双回文串

solution

利用 Manacher 的结果,可以求出以每个位置为开头/结尾的最长回文串长度。

代码还是很好写
#include <bits/stdc++.h>
using namespace std;

const int N = 200010;

int n , ans , l[N] , f1[N] , f2[N];
char s[N] , s1[N];

inline int read()
{
    int asd = 0 , qwe = 1; char zxc;
    while(!isdigit(zxc = getchar())) if(zxc == '-') qwe = -1;
    while(isdigit(zxc)) asd = asd * 10 + zxc - '0' , zxc = getchar();
    return asd * qwe;
}

int main()
{
    scanf("%s" , s1 + 1) , n = strlen(s1 + 1);
    s[0] = '~' , s[1] = '*';
    for(int i = 1;i <= n;i++) s[i * 2] = s1[i] , s[i * 2 + 1] = '*';
    n = n * 2 + 1;
    for(int i = 1 , mid = 0 , r = 0;i <= n;i++)
    {
        l[i] = i < r ? min(r - i + 1 , l[mid * 2 - i]) : 1;
        while(s[i + l[i]] == s[i - l[i]]) l[i]++;
        if(i + l[i] > r) r = i + l[i] - 1 , mid = i;
        f1[i + l[i] - 1] = max(l[i] - 1 , f1[i + l[i] - 1]);
        f2[i - l[i] + 1] = max(l[i] - 1 , f2[i - l[i] + 1]);
    }
    for(int i = 2;i <= n;i++) f1[i] = max(f1[i] , f1[i + 2] - 2);
    for(int i = 2;i <= n;i++) f2[i] = max(f2[i] , f2[i - 2] - 2);
    for(int i = 1;i <= n;i++) if(f1[i] && f2[i]) ans = max(ans , f1[i] + f2[i]);
    cout << ans;
    return 0;
}

trie#

算法步骤#

这个东西还好意思要算法步骤吗?

感觉 xxs 都随便写。

一道例题#

[FJOI2015]火星商店问题

solution

直接线段树套 01trie,思路没什么好说的,写对就可以。

代码不是很好写了,但也不难
#include <bits/stdc++.h>
using namespace std;

const int N = 40000010;

int n , m , cnt;

inline int read()
{
    int asd = 0 , qwe = 1; char zxc;
    while(!isdigit(zxc = getchar())) if(zxc == '-') qwe = -1;
    while(isdigit(zxc)) asd = asd * 10 + zxc - '0' , zxc = getchar();
    return asd * qwe;
}

struct Trie
{
    int c[N][2] , sum[N];

    inline void update(int k , int x , int tim)
    {
        for(int i = 20;i >= 0;i--)
        {
            int tmp = (x >> i) & 1;
            // cout << " :: 1 " <<  k << " " << tmp << endl;
            k = c[k][tmp] ? c[k][tmp] : (c[k][tmp] = ++cnt);
            sum[k] = max(sum[k] , tim);
        }
    }

    inline int ask(int k , int x , int tim)
    {
        int res = 0;
        // cout << k << " " << x << " " << tim << endl;
        for(int i = 20;i >= 0;i--)
        {
            int tmp = (x >> i) & 1;
            // cout << " :: 2 " << k << " " << tmp << endl;
            if(c[k][tmp ^ 1] && sum[c[k][tmp ^ 1]] >= tim) res += 1 << i , k = c[k][tmp ^ 1];
            else if(sum[c[k][tmp]] >= tim) k = c[k][tmp];
            else return res;
        }
        return res;
    }
}t;

inline void change(int p , int ls , int rs , int num , int tim , int id)
{
    t.update(p , num , tim);
    if(ls == rs) return;
    if((ls + rs) / 2 >= id) change(p * 2 , ls , (ls + rs) / 2 , num , tim , id);
    else change(p * 2 + 1 , (ls + rs) / 2 + 1 , rs , num , tim , id);
}

inline int get(int p , int l , int r , int ls , int rs , int num , int tim)
{
    if(l <= ls && rs <= r) return t.ask(p , num , tim);
    int sum = 0;
    if((ls + rs) / 2 >= l) sum = max(sum , get(p * 2 , l , r , ls , (ls + rs) / 2 , num , tim));
    if((ls + rs) / 2 <  r) sum = max(sum , get(p * 2 + 1 , l , r , (ls + rs) / 2 + 1 , rs , num , tim));
    return sum;
}

int main()
{
    n = read() , m = read() , cnt = n * 4;
    for(int i = 1;i <= n;i++)
    {
        int x = read();
        change(1 , 1 , n , x , 1e9 , i);
    }
    int tim = 0;
    for(int i = 1;i <= m;i++)
    {
        int opt = read();
        if(opt == 0)
        {
            ++tim;
            int s = read() , v = read();
            change(1 , 1 , n , v , tim , s);
        }
        else
        {
            int l = read() , r = read() , x = read() , d = read();
            cout << get(1 , l , r , 1 , n , x , max(tim - d + 1 , 0)) << endl;
        }
    }
    return 0;
}

作者:JiaY19

出处:https://www.cnblogs.com/JiaY19/p/15935914.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   JiaY19  阅读(44)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示