• 牛客寒假算法基础集训营3 I 智乃的密码(二分、尺取)


    题目链接

    题目大意:

    给定字符串 \(s\)\(L\)\(R\) ,求满足长度为 \([L, R]\) 且至少包含四类字符中的三种的子串数量。

    思路:

    当固定了区间左端点时,随着右端点向右移动对答案的贡献具有单调性。同样,固定右端点,向右移动左端点,对答案的贡献也有单调性。我们考虑使用尺取。

    固定区间的左端点为 \(i\) 时,找可行的右端点的取值范围。

    Code:
    int main() {
        ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
        int n, L, R; cin >> n >> L >> R;
        string s; cin >> s;
        map<int, int> cnt; // 四类字符的数量
     
        auto upd = [&](char ch, int dx) {
            if (isupper(ch)) {
                cnt[0] += dx;
            } else if (islower(ch)) {
                cnt[1] += dx;
            } else if (isdigit(ch)) {
                cnt[2] += dx;
            } else {
                cnt[3] += dx;
            }
            return;
        };
     
        ll ans = 0;
        for (int i = 0, j = 0; i < n; i++) { // [, )
            while (j <= n && (cnt[0] > 0) + (cnt[1] > 0) + (cnt[2] > 0) + (cnt[3] > 0) < 3) {
                if (j < n)
                    upd(s[j], 1);
                j++;
            }
            int l = max(j, i + L); // 此时j是刚好满足出现次数的位置,i + L表示符合条件的右端点至少距离长度为L
            int r = min(n, i + R); // 右端点不能取越界,且距离最多不能超过R
            ans += max(0, r - l + 1);
            upd(s[i], -1);
        }
        cout << ans << "\n";
        return 0;
    }
     
    /*
    11 3 6
    11111bAAA**
     
    7 3 6
    11111bA
     
     */
    

    左端点已经固定,假设此时第一个满足条件的右端点下标为 \(idx\),那么根据单调性在这之后的下标也可以当作右端点。我们可以二分求出第一个满足条件的右端点。这里使用前缀和加速对区间内子串数量的查询。

    int main() {
        ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
        int n, L, R; cin >> n >> L >> R;
        string s; cin >> s;
        vector<vector<int>> pre(4, vector<int>((int)s.length() + 1));
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < 4; j++)
                pre[j][i + 1] = pre[j][i];
            if (isupper(s[i]))
                pre[0][i + 1]++;
            else if (islower(s[i]))
                pre[1][i + 1]++;
            else if (isdigit(s[i]))
                pre[2][i + 1]++;
            else
                pre[3][i + 1]++;
        }
    
        auto check = [&](int l, int r) {
            int cnt = 0;
            for (int i = 0; i < 4; i++) {
                cnt += (pre[i][r] - pre[i][l - 1] > 0);
            }
            return cnt >= 3;
        };
    
        ll ans = 0;
        for (int i = 1; i <= n; i++) {
            int l = i, r = min(i + R, n + 1), idx = 0;
            while (l < r) {
                int mid = (l + r) >> 1; // 找第一个满足条件的右端点
                if (check(i, mid)) {
                    idx = mid;
                    r = mid;
                } else {
                    l = mid + 1;
                }
            }
            if (idx == 0) // 一个满足条件的都没找到
                continue;
            l = max(idx, i + L - 1);
            r = min(n, i + R - 1);
            // cerr << "l = " << l << ", r = " << r << endl;
            ans += max(0, r - l + 1);
        }
        cout << ans << "\n";
        return 0;
    }
    
  • 相关阅读:
    dijkstra 为什么不能解决负权边?
    Asteroids poj3041
    Dining poj3281
    炮兵阵地
    玉米田Corn Fields
    互不侵犯
    Golang---内存管理(内存分配)
    单例模式-Singleton
    HTTP2.0 学习
    Golang---GMP调度策略
  • 原文地址:https://www.cnblogs.com/Nepenthe8/p/15863640.html
Copyright © 2020-2023  润新知