1 条题解

  • 0
    @ 2026-4-28 20:21:28

    A. 全长度减法 题解


    一、题意理解

    给定一个长度为 nn 的排列 pp。对每个 k=1,2,,nk = 1, 2, \dots, n(按顺序),必须恰好选择一个长度为 kk 的子数组,将其中所有元素减 11。目标是使最终所有元素变为 00。判断是否可行。


    二、关键观察

    2.1 最大值 nn 的要求

    元素 nn 需要被减 nn 次才能变为 00。由于总共只有 nn 次操作,这意味着每次操作都必须包含元素 nn

    因此,所有子数组的公共交集必须包含 nn 的位置。

    2.2 次大值 n1n-1 的要求

    元素 n1n-1 需要被减 n1n-1 次。它最多被排除在一次操作之外(因为总共有 nn 次操作,它必须参与其中 n1n-1 次)。

    • 长度为 11 的子数组只会包含 nn(由上一步可知),因此可以排除 n1n-1
    • 长度为 22 的子数组必须同时包含 nnn1n-1,否则 n1n-1 将少参与一次操作。

    因此,n1n-1 必须与 nn 相邻

    2.3 一般规律

    对于任意值 ii(从 nn 向下考虑),它需要被减 ii 次,因此最多被排除在 nin - i 次操作之外。

    • 长度为 1,2,,ni1, 2, \dots, n-i 的子数组可以排除 ii
    • 长度为 ni+1n-i+1 的子数组必须包含 ii

    这意味着所有值 {i,i+1,,n}\{i, i+1, \dots, n\} 必须包含在长度为 ni+1n-i+1 的子数组中,因此它们在原排列中的位置必须构成一个连续段

    2.4 等价条件

    排列 pp 可行,当且仅当满足以下条件:

    对于每个 ii11nn,值 {i,i+1,,n}\{i, i+1, \dots, n\} 的位置构成一个连续段。

    等价地,排列必须是单峰的(mountain):先递增后递减。

    也就是说,从排列的两端交替取最小值,可以依次取出 1,2,,n1, 2, \dots, n


    三、算法流程

    我们可以模拟以下过程:

    1. 设置左指针 l=0l = 0,右指针 r=n1r = n-1
    2. 对于 i=1,2,,ni = 1, 2, \dots, n
      • 如果 p[l]=ip[l] = i,说明 ii 在左端,将 ll 右移一位。
      • 否则,如果 p[r]=ip[r] = i,说明 ii 在右端,将 rr 左移一位。
      • 否则,排列不是单峰的,输出 NO 并返回。
    3. 如果循环顺利完成,输出 YES

    四、正确性证明

    • 充分性:如果排列满足上述条件,我们可以按以下方式构造操作:

      • 对于 k=nk = n,选择整个数组减 11
      • 对于 k=n1k = n-1,选择除去已归零的那一端元素后的子数组。
      • 依此类推,每次从两端逐步收缩子数组。
    • 必要性:已在上文通过分析每个值需要参与的操作次数证明。


    五、标程解析

    void solve() {
      int n;
      std::cin >> n;
      std::vector<int> p(n);
      for (int i = 0; i < n; i++) {
        std::cin >> p[i];
      }  
     
      int l = 0, r = n - 1;
      for (int i = 1; i <= n; i++) {
        if (p[l] == i) {
          l++;
        } else if (p[r] == i) {
          r--;
        } else {
          NO;
          return;
        }
      }
      YES;
    }
    

    步骤

    1. 读入排列 pp
    2. 初始化左右指针 l=0l = 0r=n1r = n-1
    3. 从小到大枚举 i=1i = 1nn
      • 如果当前左端是 ii,则"移除"左端(l++)。
      • 如果当前右端是 ii,则"移除"右端(r--)。
      • 如果都不是,说明排列不满足条件,输出 NO
    4. 全部成功移除则输出 YES

    时间复杂度 O(n)O(n),总复杂度 O(tn)O(t \cdot n)


    六、样例验证

    样例 1

    4
    1 3 4 2
    
    • i=1i=1p[l]=1p[l]=1l=1l=1
    • i=2i=2p[r]=2p[r]=2r=2r=2
    • i=3i=3p[l]=3p[l]=3l=2l=2
    • i=4i=4p[l]=4p[l]=4 → 完成 → YES

    样例 2

    5
    1 5 2 4 3
    
    • i=1i=1p[l]=1p[l]=1l=1l=1
    • i=2i=2p[l]=52p[l]=5 \neq 2p[r]=32p[r]=3 \neq 2NO

    样例 3

    5
    2 4 5 3 1
    
    • i=1i=1p[r]=1p[r]=1r=3r=3
    • i=2i=2p[l]=2p[l]=2l=1l=1
    • i=3i=3p[r]=3p[r]=3r=2r=2
    • i=4i=4p[l]=4p[l]=4l=2l=2
    • i=5i=5p[l]=5p[l]=5 → 完成 → YES

    样例 4

    3
    3 1 2
    
    • i=1i=1p[l]=31p[l]=3 \neq 1p[r]=21p[r]=2 \neq 1NO

    七、代码实现(C++)

    #include <bits/stdc++.h>
    using namespace std;
    
    void solve() {
        int n;
        cin >> n;
        vector<int> p(n);
        for (int i = 0; i < n; i++) {
            cin >> p[i];
        }
        
        int l = 0, r = n - 1;
        for (int i = 1; i <= n; i++) {
            if (p[l] == i) {
                l++;
            } else if (p[r] == i) {
                r--;
            } else {
                cout << "NO\n";
                return;
            }
        }
        cout << "YES\n";
    }
    
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
        
        int t;
        cin >> t;
        while (t--) {
            solve();
        }
        return 0;
    }
    

    八、复杂度分析

    • 每个测试用例仅需 O(n)O(n) 时间扫描排列一次。
    • n100n \leq 100t100t \leq 100,总复杂度极小。
    • 空间复杂度 O(n)O(n)
    • 1

    信息

    ID
    6686
    时间
    1000ms
    内存
    256MiB
    难度
    2
    标签
    递交数
    1
    已通过
    1
    上传者