ABC381 C-E题解

C - 11/22 Substring

枚举每个 /,从 / 出发向左右两边扩展到最远。因为每个点最多能被访问一次(向右只扩展 2,向左只扩展 1),复杂度为 O(n)

int ans = 0;
for (int i = 0; i < n; i++) {
    if (s[i] != '/') continue;
    int l = i - 1, r = i + 1, len = 1;
    while (l >= 0 && r < n && s[l] == '1' && s[r] == '2')
        len += 2, l--, r++;
    ans = max(ans, len);
}
cout << ans << '\n';

D - 1122 Substring

考虑从头开始遍历,如果当前 aiai+1 则跳过(每次往后走一步),ai=ai+1 则开始记录(每次往后走两步),直到 不相等 或者 出现了重复的数字。如果是前者,统计答案,清空长度即可。对于后者,需要开一个 unordered_map 记录当前 1122 数列里每个数字出现的位置。一旦重复,就更新答案,然后把当前 1122 数列的开头 st 到重复数字第一次出现的位置 ed 中间的数字全部扔掉,sted+2 继续向后找。因为每个数字最多被扔掉一次,所以复杂度仍为 O(n)

但是发现这个算法没法处理 11122 这种情况,它注意不到 1122 的部分,而是在 1112 失配之后直接跳到 22 了。想到我们可以按开头位置的奇偶性分开处理。强制开头为奇数跑一遍,强制开头为偶数跑一遍,最后答案取最大值即可。

int ans = 0;
auto startFrom = [&](int x) {
    int st = -1;
    map<int, int> mp;
    for (int i = x; i < n; i += 2) {
        if (i == n - 1 || a[i] != a[i + 1]) { // 如果i=n-1则肯定失配
            if (st != -1) {
                ans = max(ans, i - st);
                st = -1;
                mp.clear();
            }
            continue;
        }
        if (st == -1) st = i;
        if (mp.count(a[i])) {
            ans = max(ans, i - st);
            int ed = mp[a[i]];
            for (int j = st; j <= ed; j += 2)
                mp.erase(a[j]); // 清除st~ed之间的数
            st = ed + 2;
        }
        mp[a[i]] = i;
    }
    if (st != -1) ans = max(ans, n - st);
};
startFrom(0), startFrom(1);
cout << ans << '\n';

E - 11/22 Subsequence

朴素的想法是枚举每个区间中的 /,用前缀和计算它前面的 1 的个数 cnt1 和后面的 2 的个数 cnt2,用 min(cnt1,cnt2) 更新答案,但枚举 /O(n) 的,复杂度过高。

注意到从前到后枚举区间内 / 的过程中,cnt1 单调增加,cnt2 单调减少,min(cnt1,cnt2) 的最大值一定取在 cnt1<cnt2cnt1>cnt2 的交界处(之前 cnt1 是最小值,不断增大;之后 cnt2 是最小值,不断减小)。这个过程是有单调性的,可以二分,复杂度为 O(qlogn)

int n, q;
std::string s;
std::cin >> n >> q >> s;
std::array<std::vector<int>, 2> cnt{std::vector<int>(n), std::vector<int>(n)}; // 12前缀和
std::vector<int> pos; // 记录/的位置
for (int i = 0; i < n; i++) {
    s[i] == '/' ? pos.emplace_back(i) : cnt[s[i] - '1'][i]++;
}
std::partial_sum(cnt[0].begin(), cnt[0].end(), cnt[0].begin()); // 自动前缀和
std::partial_sum(cnt[1].begin(), cnt[1].end(), cnt[1].begin());
auto getSum = [&](int i, int l, int r) {
    return cnt[i][r] - (l ? cnt[i][l - 1] : 0);
};
auto calc = [&](int l, int r) -> int {
    int L = std::lower_bound(pos.begin(), pos.end(), l) - pos.begin(); // 区间内第一个/
    int R = std::upper_bound(pos.begin(), pos.end(), r) - pos.begin() - 1; // upper_bound求出大于r的第一个/,-1得到区间内的最后一个/
    if (R < L) return 0; // 区间内没有/
    int i = *std::ranges::partition_point(std::ranges::iota_view(L, R), [&](int x) -> bool {
        return getSum(0, l, pos[x]) <= getSum(1, pos[x], r);
    }); // 二分,找到第一个cnt1>cnt2的位置
    int res = 0; // min(cnt1,cnt2)的最大值
    if (i <= R) res = std::max(res, std::min(getSum(0, l, pos[i]), getSum(1, pos[i], r)));
    if (i >= L + 1) res = std::max(res, std::min(getSum(0, l, pos[i - 1]), getSum(1, pos[i - 1], r))); // 也可能在最后一个cnt1<=cnt2的位置上取得,峰的位置不一定在哪边
    return res * 2 + 1;
};
for (int l, r; q--;) {
    std::cin >> l >> r;
    std::cout << calc(l - 1, r - 1) << '\n';
}

因为有大小相等的点,不能三分。

posted @   XYukari  阅读(7)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示