题解:P9244 [蓝桥杯 2023 省 B] 子串简写

题意

给定一个长度为 $ n $ 的字符串 $ S $,统计满足以下条件的子串数量:

  • 子串的首字符为 $ c_1 $;
  • 子串的尾字符为 $ c_2 $;
  • 子串的长度大于等于 $ k $。

思路

法一:二分

设字符串的长度为 \(n\),记两个特定字符为 $ c_1 $ 与 $ c_2 $。定义数组

\[P_{c_1}=\{\,i\in\{1,2,\dots,n\}\mid \text{字符串的第 \(i\) 个字符为 } c_1\,\} \]

\[P_{c_2}=\{\,j\in\{1,2,\dots,n\}\mid \text{字符串的第 \(j\) 个字符为 } c_2\,\} \]

注意到 $ P_{c_1} $ 与 $ P_{c_2} $ 中的元素均按从小到大排列。

\(N(i)=\{\,j\in P_{c_2}\mid j\ge i+k\,\}\)

由于 $ P_{c_2} $ 为递增序列,令 $ j_0 $ 为 $ P_{c_2} $ 中第一个满足 $ j_0\ge i+k $ 的元素,则从 $ j_0 $ 开始所有元素均属于 $ N(i) $ ,其个数为 $ |N(i)| $ 。

因此,最终答案

\[ans=\sum_{i\in P_{c_1}}|N(i)| \]

在实际求解中,可利用二分查找在 $ P_{c_2} $ 中确定每个 $ i $ 对应的 $ j_0 $,因此整体时间复杂度为 $ O(n\log n) $,可 AC 本题。

#include <iostream>
#include <vector>
#define int long long
using namespace std;

string S;
char c1, c2;
int k;
vector<int> pos_c1, pos_c2;

signed main()
{
    cin.tie(0)->sync_with_stdio(0);
    cin >> k >> S >> c1 >> c2;
    for (int i = 0; i < S.size(); i++) {
        if (S[i] == c1) pos_c1.push_back(i);
        if (S[i] == c2) pos_c2.push_back(i);
    }
    int ans = 0;
    for (auto it : pos_c1) {
        ans += pos_c2.end() - lower_bound(pos_c2.begin(), pos_c2.end(), it + k - 1);
    }

    cout << ans;

    return 0;
}


法二:单调队列

在法一的启发下,使用队列表示 $ P_{c_1} $ 及 $ P_{c_2} $。对于每个 $ i\in P_{c_1} $,定义阈值 $ T=i+k-1 $。

考虑 $ P_{c_2} $ 的递增顺序,对于每个 $ i $,将队列 $ P_{c_2} $ 中的不满足 \(j\ge T\) 的队首元素 $ j $ 舍去。设舍去操作后剩下的 $ P_{c_2} $ 中的元素个数为 $ |P_{c_2}(i)| $。则当前以 $ i $ 为首字符的合法组合即有 $ |P_{c_2}(i)| $ 个,最终答案 $ ans $ 为:

\[ans=\sum_{i\in P_{c_1}}|P_{c_2}(i)| \]

由于在整个过程中,每个 $ P_{c_2} $ 的元素最多被处理一次,故该方法的时间复杂度为 $ O(n) $。

#include <iostream>
#include <queue>
#define int long long
using namespace std;

string S;
char c1, c2;
int k;
queue<int> pos_c1, pos_c2;

signed main()
{
    cin.tie(0)->sync_with_stdio(0);
    cin >> k >> S >> c1 >> c2;
    for (int i = 0; i < S.size(); i++) {
        if (S[i] == c1) pos_c1.push(i);
        if (S[i] == c2) pos_c2.push(i);
    }
    int ans = 0;
    while (!pos_c1.empty()) {
        int top = pos_c1.front();
        pos_c1.pop();
        while (!pos_c2.empty() and pos_c2.front() < top + k - 1) pos_c2.pop();
        ans += pos_c2.size();
    }
    cout << ans;

    return 0;
}


posted @ 2025-03-26 16:55  minatomorin  阅读(49)  评论(0)    收藏  举报