牛客寒假算法基础集训营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;
}
上一篇:207. 课程表


下一篇:LeetCode - Course Schedule IV