CF1156E Special Segments of Permutation 题解 分治

题目链接

http://codeforces.com/problemset/problem/1156/E

题目大意

给定一个长度为 \(n\) 的排列 \(p\)\(p\)\(1, 2, \ldots, n\) 组成且在排列 \(p\) 中数字 \(1 \sim n\) 各出现了恰好一次)。

我们定义排列 \(p\) 的一个子段 \(p[l, r]\) 为一个 特殊子段 当且仅当 \(p_l + p_r = \max \limits_{i = l}^{r} p_i\)

请计算排列 \(p\) 中特殊子段的数量。

解题思路

分治。

考虑区间 \([l, r]\) 分成两端 \([l, mid]\)\([mid+1, r]\)

且左端点在 \([l, mid]\) 中,右端点在 \([mid+1, r]\) 中,然后使用双指针对于特定区间 \([i, j]\)

先假设最大值在 \([mid+1, j]\) 中(设为 \(mx\)),则可以得到最小的一个 \(i\),同时将 \([i, mid]\) 区间对应的数值加入一个 set 中,对于 \(p_j\),若 \(mx - p_j\) 在 set 中,则说明 \([i, mid]\) 中存在一个特殊子段的左端点。

同样的逻辑(假设最大值在 \([i, mid]\))中,……

然后再分治左右子区间。

示例程序

#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 5;

int n, p[maxn], ans;

void cal(int l, int r) {
    if (l == r) return;
    int mid = (l + r) / 2;
    cal(l, mid), cal(mid+1, r);
    set<int> st;
    for (int i = mid, j = mid+1, mx = p[j]; j <= r; mx = max(mx, p[++j])) {
        while (i >= l && p[i] < mx) {
            st.insert(p[i--]);
        }
        if (st.count(mx - p[j])) ans++;
    }
    st.clear();
    for (int i = mid, j = mid+1, mx = p[i]; i >= l; mx = max(mx, p[--i])) {
        while (j <= r && p[j] < mx) {
            st.insert(p[j++]);
        }
        if (st.count(mx - p[i])) ans++;
    }
}

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) scanf("%d", p+i);
    cal(1, n);
    printf("%d\n", ans);
    return 0;
}
posted @ 2023-03-17 01:02  quanjun  阅读(15)  评论(0编辑  收藏  举报