字符串算法习题分析

文章介绍了如何解决三个与字符串相关的问题:POI2006竞赛中的OKR-PeriodsofWords问题,涉及前缀周期和公共前后缀;同构字符串判断,检查两个字符串是否满足映射关系;以及CampSchedule问题,构造01字符串以包含特定子串并保持字符数量平衡。
摘要由CSDN通过智能技术生成

1 [POI2006]OKR-Periods of Words

P3435 [POI2006] OKR-Periods of Words - 洛谷

题目描述

定义 Q Q Q A A A 的周期,当且仅当 Q Q Q A A A 的严格前缀,且 A A A Q + Q Q + Q Q+Q 的前缀( A A A 可以等于 Q + Q Q+Q Q+Q)。特别地,如果没有满足的 Q Q Q A A A 的周期为空串。

给出一个串,求出其所有前缀的最大周期长度之和。

题解

举个例子: A = abcabcab A=\texttt{abcabcab} A=abcabcab Q max ⁡ = abcabc Q_{\max}=\texttt{abcabc} Qmax=abcabc

实际上是把 abcabcab \texttt{abcabcab} abcabcab 拆成了 abcabc + ab \texttt{abcabc} + \texttt{ab} abcabc+ab。而且,为了满足题目要求, ab \texttt{ab} ab 还要是 abcabc \texttt{abcabc} abcabc 的前缀。所以 ab \texttt{ab} ab 应当是 A A A 的公共前后缀。

不难发现,例子里的 ab \texttt{ab} ab 就是 A A A最短公共前后缀,这样才能使周期最长。所以长度为 i i i 的前缀的最长周期是(这里公共前后缀就用 N e x t Next Next 表示):
max ⁡ { ∣ Q i ∣ } = i − min ⁡ { N e x t i } \max\{|Q_i|\}=i-\min\{Next_i\} max{Qi}=imin{Nexti}
怎么求 min ⁡ { N e x t i } \min\{Next_i\} min{Nexti} 呢?如果 N e x t i Next_i Nexti 是前缀 A i A_i Ai 的一个公共前后缀的长度,则 N e x t N e x t i Next_{Next_i} NextNexti 一定也是 A i A_i Ai 的一个公共前后缀的长度; N e x t N e x t N e x t i … Next_{Next_{Next_i}}\dots NextNextNexti 可以用 m i n i min_i mini 记录前缀 A i A_i Ai 的最短公共前后缀:
m i n i = { m i n N e x t i m i n N e x t i ≠ 0 N e x t i m i n N e x t i = 0 min_i= \begin{cases} min_{Next_i} &min_{Next_i}\ne 0\\ Next_i &min_{Next_i}=0 \end{cases} mini={minNextiNextiminNexti=0minNexti=0
解释:通过KMP算法可得, N e x t i Next_i Nexti 为前缀 A i A_i Ai 的最长公共前后缀,所以 m i n N e x t i min_{Next_i} minNexti 一定是 A i A_i Ai 所有的公共前后缀的最小值;如果 m i n N e x t i min_{Next_i} minNexti 0 0 0 ,为了先找到满足题意的周期,再考虑无合法周期,所以要等于 N e x t i Next_i Nexti (当然,如果 N e x t i Next_i Nexti 仍然等于 0 0 0 ,则说明前缀 A i A_i Ai 确实没有合法周期,此时算作空串)。

最后答案:
a n s = ∑ i = 1 ∣ m i n i ≠ 0 ∣ A ∣ ( i − m i n i ) ans=\sum_{i=1|min_i\ne0}^{|A|}(i-min_i) ans=i=1∣mini=0A(imini)

\\some code...
int main() {
    CLOSE;
    cin >> n >> s;
    s = " " + s;
    get_next(s);
    long long ans = 0;
    for (int i = 1; i <= n; i++) {
        minn[i] = minn[Next[i]];
        if (!minn[i])
            minn[i] = Next[i];
        if (minn[i])
            ans += i - minn[i];
    }
    cout << ans;
    return 0;
}

2 同构字符串

题目描述

同构字符串定义:若两个字符串 S S S T T T ( ∣ S ∣ = ∣ T ∣ |S|=|T| S=T),满足一种映射关系 f ( x ) f(x) f(x),使得 ∀ 1 ≤ i ≤ ∣ S ∣ , f ( S i ) = T i \forall 1\le i\le |S|,f(S_i)=T_i ∀1iS,f(Si)=Ti,则称 S S S T T T 是同构字符串,或者说 S S S T T T 同构。

例如, abaac \texttt{abaac} abaac xkxxz \texttt{xkxxz} xkxxz 同构,但与 nmmtt \texttt{nmmtt} nmmtt 不同构。

给定一个长为 n n n 的字符串 s s s (仅由小写字母组成)和 m m m 次询问,每次询问给出 ( x , y , l e n ) (x,y,len) (x,y,len),问 s x ∼ x + l e n − 1 s_{x\sim x+len-1} sxx+len1 是否和 s y ∼ y + l e n − 1 s_{y\sim y+len-1} syy+len1 同构。

题解

字符串 s s s 中每一位都由小写字母组成,所以不妨考虑用26个01串表示每一个字母的出现位置 0 \texttt 0 0 表示未出现, 1 \texttt 1 1 表示出现。分别对 s x s_x sx s y s_y sy 各求出 26 26 26 个01串,比较其能否一一对应。

如果能够对应,则证明 s x s_x sx 中每一种出现过的字母的位置关系都可以与 s y s_y sy 中的一个字母的位置重合,这两个字母满足映射关系,一一对应说明每一个字母都有映射关系,满足题意。

比如,若 s x = acbaa , s y = xzyxx s_x=\texttt{acbaa},s_y=\texttt{xzyxx} sx=acbaa,sy=xzyxx

字母 s x s_x sx字母 s y s_y sy
a \texttt{a} a 10011 \texttt{10011} 10011 x \texttt{x} x 10011 \texttt{10011} 10011
b \texttt{b} b 00100 \texttt{00100} 00100 y \texttt{y} y 00100 \texttt{00100} 00100
c \texttt{c} c 01000 \texttt{01000} 01000 z \texttt{z} z 01000 \texttt{01000} 01000
其他 00000 \texttt{00000} 00000其他 00000 \texttt{00000} 00000

其所有字母的位置都一一对应,满足映射关系,则 s x s_x sx s y s_y sy 同构。

//some code...
HASH H[30];
int main() {
    CLOSE;
    cin >> n >> m >> s;
    for (int i = 1; i <= 26; i++)
        H[i].init(128, mod), t[i] = " ";
    s = " " + s;
    for (int i = 1; i <= n; i++)  //预处理出s的所有字母的01串
        for (char j = 'a'; j <= 'z'; j++)
            t[j - 'a' + 1] += (s[i] == j ? "1" : "0");
    for (int i = 1; i <= 26; i++)
        H[i].make(t[i]);
    while (m--) {
        cin >> x >> y >> len;
        for (int i = 1; i <= 26; i++) {  //截取出sx和sy的每个字母的01串
            hh1[i] = H[i].get(x, x + len - 1);  
            hh2[i] = H[i].get(y, y + len - 1);
        }
        sort(hh1 + 1, hh1 + 27);  //排序比较能否一一对应
        sort(hh2 + 1, hh2 + 27);
        int flag = 0;
        for (int i = 1; i <= 26; i++)
            if (hh1[i] != hh2[i])
                flag = 1;
        if (flag)
            cout << "NO" << endl;
        else
            cout << "YES" << endl;
    }
    return 0;
}

3 Camp Schedule

Camp Schedule - 洛谷

题目描述

给定两个01字符串 s , t s,t s,t,求01串 k k k ,要求 k k k 中的 0 \texttt 0 0 1 \texttt 1 1 的个数分别于 s s s 相等,且 k k k 中包含子串 t t t 的个数尽可能地多。

题解

求出 t t t 的循环节 Q Q Q,将 t t t 拆成 q + Q q + Q q+Q 的形式,则 k = q + Q + Q + Q + … k=q+Q+Q+Q+\dots k=q+Q+Q+Q+ 时包含 t t t 的个数最多。

最后注意考虑多余的 0 \texttt 0 0 1 \texttt 1 1 的问题。

//some code...
void get01(int& s0, int& s1, string s) {
    s0 = 0, s1 = 0;
    for (int i = 0; i < s.size(); i++) {
        if (s[i] == '0')
            s0++;
        else
            s1++;
    }
}
int main() {
    CLOSE;
    cin >> s >> t;
    int s0, s1, t0, t1;
    get01(s0, s1, s);
    get01(t0, t1, t);
    t = " " + t;
    get_next(t);
    string t2 = t.substr(Next[t.size() - 1] + 1, t.size() - Next[t.size() - 1] - 1); //取出循环节
    int t20 = 0, t21 = 0;
    get01(t20, t21, t2);
    if (t0 <= s0 && t1 <= s1) {
        s1 -= t1;
        s0 -= t0;
        cout << t.substr(1, t.size() - 1);
    }
    while (s0 - t20 >= 0 && s1 - t21 >= 0) { //重复循环节
        cout << t2;
        s0 -= t20;
        s1 -= t21;
    }
    for (int i = 1; i <= s1; i++) cout << '1';  //剩余的0和1输出
    for (int i = 1; i <= s0; i++) cout << '0';
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值