Loading

字符串乱记

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

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

最小表示法

定义:

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

形式化定义为:

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

\[S[i...n]+S[1...i-1]=T \]

则称 \(S\)\(T\) 循环同构

举一个通俗易懂的例子:

例如有一个字符串:\(absdc\)

它的循环同构有 \(bsdca\)\(sdcab\)\(dcabs\)\(cabsd\)

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

做法

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

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

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

接着比较 \(s_{i+k}\)\(s_{j+k}\)

假设 \(s_{i+k}<s_{j+k}\) ,则显然 \(j-j+k\) 均不能成为起始位置,直接令 \(j\gets j+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);
}

一道例题

\(\text{P5211 [ZJOI2017]字符串}\)

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

以后做了,再补吧。

Manacher

算法步骤

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

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

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

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

\(abba\)\(aba\)

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

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

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

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

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

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

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

至于如何求对称点,小学奥数的中点公式: \(l+r=mid*2 \gets l=mid*2-r\)

即:

\[len_j \geq \min(len_{2\times pos-j},mx-j) \]

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

显然 \(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]);
}

两道例题

\(\text{P5446 [THUPC2018]绿绿和串串}\)

solution

合法的串 \(S\) 一定是 \(T\) 的前缀。先用 \(\text{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;
}

\(\text{P4555 [国家集训队]最长双回文串}\)

solution

利用 \(\text{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

算法步骤

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

感觉 \(\text{xxs}\) 都随便写。

一道例题

\(\text{[FJOI2015]火星商店问题}\)

solution

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

代码不是很好写了,但也不难
#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;
}
posted @ 2022-02-25 14:24  JiaY19  阅读(43)  评论(0编辑  收藏  举报