我们惺惺相惜

[题目链接](https://www.nowcoder.com/practice/e5cb693b91fe405e8aa233503b7fced5)

思路

核心转化:Dilworth 定理

题目要求判断子数组 能否分成两个非空的严格递增子序列。由 Dilworth 定理,一个序列能被划分成至多 条严格递增子序列,当且仅当最长非递增子序列的长度不超过

注意,如果子数组本身就是严格递增的,我们也可以把它拆成两个非空部分,两部分各自严格递增。因此只要子数组长度 且能被至多 2 条严格递增子序列覆盖,答案就是 YES。

等价条件:子数组 是"好区间" 且不存在三元组 满足

如何高效判断?

对每个位置 ,预处理两个值:

  • :最大的 使得 (不存在则为 )。
  • :最小的 使得 (不存在则为 )。

这两个数组都可以用单调栈 时间内求出。

有了这两个数组,判定条件变为:子数组 中存在非递增三元组 存在某个 ,使得

离线 + 线段树

将所有询问按 从大到小排序。从 递减到

  1. 当处理到 时,把所有满足 的位置 激活,在线段树的 处插入
  2. 对于 值等于当前 的询问 ,在线段树上查询 的最小值。若最小值 ,说明存在非递增三元组,答案为 NO;否则为 YES。

正确性解释: 递减时,条件 等价于 范围内。由于 是最靠右的满足 的位置,一旦 ,就保证在 中确实存在 的元素。线段树维护的是所有已激活位置的 最小值,查询 恰好是在检查有没有合法的中间点

举例

以样例 ,查询 为例:

  • ),
  • 时, 已激活(因 ),查询 得到

存在三元组 ,所以答案 NO。

代码

#include <bits/stdc++.h>
using namespace std;

struct SegTree {
    int n;
    vector<int> tree;
    void init(int _n) {
        n = _n;
        tree.assign(4 * n, INT_MAX);
    }
    void update(int pos, int val, int node, int lo, int hi) {
        if (lo == hi) {
            tree[node] = min(tree[node], val);
            return;
        }
        int mid = (lo + hi) / 2;
        if (pos <= mid) update(pos, val, 2*node, lo, mid);
        else update(pos, val, 2*node+1, mid+1, hi);
        tree[node] = min(tree[2*node], tree[2*node+1]);
    }
    void update(int pos, int val) { update(pos, val, 1, 1, n); }
    int query(int l, int r, int node, int lo, int hi) {
        if (l > r) return INT_MAX;
        if (l <= lo && hi <= r) return tree[node];
        int mid = (lo + hi) / 2;
        int res = INT_MAX;
        if (l <= mid) res = min(res, query(l, r, 2*node, lo, mid));
        if (r > mid) res = min(res, query(l, r, 2*node+1, mid+1, hi));
        return res;
    }
    int query(int l, int r) {
        if (l > r) return INT_MAX;
        return query(l, r, 1, 1, n);
    }
};

int main() {
    int T;
    scanf("%d", &T);
    while (T--) {
        int n, q;
        scanf("%d%d", &n, &q);
        vector<int> a(n + 1);
        for (int i = 1; i <= n; i++) scanf("%d", &a[i]);

        vector<int> prev_ge(n + 1, 0);
        {
            stack<int> stk;
            for (int j = 1; j <= n; j++) {
                while (!stk.empty() && a[stk.top()] < a[j]) stk.pop();
                if (!stk.empty()) prev_ge[j] = stk.top();
                stk.push(j);
            }
        }

        vector<int> next_le(n + 1, n + 1);
        {
            stack<int> stk;
            for (int j = n; j >= 1; j--) {
                while (!stk.empty() && a[stk.top()] > a[j]) stk.pop();
                if (!stk.empty()) next_le[j] = stk.top();
                stk.push(j);
            }
        }

        vector<vector<int>> by_prev(n + 1);
        for (int j = 1; j <= n; j++) {
            if (prev_ge[j] > 0) by_prev[prev_ge[j]].push_back(j);
        }

        struct Query { int l, r, idx; };
        vector<Query> queries(q);
        for (int i = 0; i < q; i++) {
            scanf("%d%d", &queries[i].l, &queries[i].r);
            queries[i].idx = i;
        }
        sort(queries.begin(), queries.end(), [](const Query& a, const Query& b) {
            return a.l > b.l;
        });

        SegTree seg;
        seg.init(n);
        vector<bool> ans(q);
        int qi = 0;

        for (int l = n; l >= 1; l--) {
            for (int j : by_prev[l]) seg.update(j, next_le[j]);
            while (qi < q && queries[qi].l == l) {
                int r = queries[qi].r;
                int idx = queries[qi].idx;
                if (r - l + 1 < 2) {
                    ans[idx] = false;
                } else {
                    int mn = seg.query(l + 1, r - 1);
                    ans[idx] = (mn > r);
                }
                qi++;
            }
        }

        for (int i = 0; i < q; i++) puts(ans[i] ? "YES" : "NO");
    }
    return 0;
}
import java.util.*;
import java.io.*;

public class Main {
    static int[] tree;
    static int N;

    static void init(int n) {
        N = n;
        tree = new int[4 * n + 4];
        Arrays.fill(tree, Integer.MAX_VALUE);
    }

    static void update(int pos, int val, int node, int lo, int hi) {
        if (lo == hi) { tree[node] = Math.min(tree[node], val); return; }
        int mid = (lo + hi) / 2;
        if (pos <= mid) update(pos, val, 2 * node, lo, mid);
        else update(pos, val, 2 * node + 1, mid + 1, hi);
        tree[node] = Math.min(tree[2 * node], tree[2 * node + 1]);
    }

    static void update(int pos, int val) { update(pos, val, 1, 1, N); }

    static int query(int l, int r, int node, int lo, int hi) {
        if (l > r) return Integer.MAX_VALUE;
        if (l <= lo && hi <= r) return tree[node];
        int mid = (lo + hi) / 2;
        int res = Integer.MAX_VALUE;
        if (l <= mid) res = Math.min(res, query(l, r, 2 * node, lo, mid));
        if (r > mid) res = Math.min(res, query(l, r, 2 * node + 1, mid + 1, hi));
        return res;
    }

    static int query(int l, int r) {
        if (l > r) return Integer.MAX_VALUE;
        return query(l, r, 1, 1, N);
    }

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringBuilder sb = new StringBuilder();
        int T = Integer.parseInt(br.readLine().trim());
        while (T-- > 0) {
            StringTokenizer st = new StringTokenizer(br.readLine());
            int n = Integer.parseInt(st.nextToken());
            int q = Integer.parseInt(st.nextToken());
            int[] a = new int[n + 1];
            st = new StringTokenizer(br.readLine());
            for (int i = 1; i <= n; i++) a[i] = Integer.parseInt(st.nextToken());

            int[] prevGe = new int[n + 1];
            Deque<Integer> stk = new ArrayDeque<>();
            for (int j = 1; j <= n; j++) {
                while (!stk.isEmpty() && a[stk.peek()] < a[j]) stk.pop();
                if (!stk.isEmpty()) prevGe[j] = stk.peek();
                stk.push(j);
            }

            int[] nextLe = new int[n + 1];
            Arrays.fill(nextLe, n + 1);
            stk.clear();
            for (int j = n; j >= 1; j--) {
                while (!stk.isEmpty() && a[stk.peek()] > a[j]) stk.pop();
                if (!stk.isEmpty()) nextLe[j] = stk.peek();
                stk.push(j);
            }

            @SuppressWarnings("unchecked")
            List<Integer>[] byPrev = new ArrayList[n + 1];
            for (int i = 0; i <= n; i++) byPrev[i] = new ArrayList<>();
            for (int j = 1; j <= n; j++) {
                if (prevGe[j] > 0) byPrev[prevGe[j]].add(j);
            }

            int[][] queries = new int[q][3];
            for (int i = 0; i < q; i++) {
                st = new StringTokenizer(br.readLine());
                queries[i][0] = Integer.parseInt(st.nextToken());
                queries[i][1] = Integer.parseInt(st.nextToken());
                queries[i][2] = i;
            }
            Arrays.sort(queries, (x, y) -> y[0] - x[0]);

            init(n);
            boolean[] ans = new boolean[q];
            int qi = 0;

            for (int l = n; l >= 1; l--) {
                for (int j : byPrev[l]) update(j, nextLe[j]);
                while (qi < q && queries[qi][0] == l) {
                    int r = queries[qi][1];
                    int idx = queries[qi][2];
                    if (r - l + 1 < 2) {
                        ans[idx] = false;
                    } else {
                        int mn = query(l + 1, r - 1);
                        ans[idx] = (mn > r);
                    }
                    qi++;
                }
            }

            for (int i = 0; i < q; i++) sb.append(ans[i] ? "YES" : "NO").append('\n');
        }
        System.out.print(sb);
    }
}

复杂度分析

  • 时间复杂度。单调栈预处理 ,线段树每个位置至多更新一次 ,每次查询
  • 空间复杂度。存储数组、单调栈预处理结果、线段树和查询。