NOIP 模拟赛 8 总结

分数:\(100 + 0 + 0 + 0 = 100\)

永康喵喵坠机了!

别样的数数大战。

T1

这道题本身非常糖,但是我更糖。

考虑 \(n \le 5000\),我们可以一边遍历 \(a\),设当前遍历到了 \(a_i\),记录 \(a_1 + a_{i-1}, a_2 + a_{i-1}, \cdots, a_{i-1} + a_{i-1}\),然后再遍历 \(a_1, a_2, \cdots, a_{i-1}\),看看有没有 \(a_j\) 能与已经记录的二元组的和凑出 \(a_i\)。但是关键就在记录二元组的和的方式。如果采用数组或 vector,查询符合条件的二元组是否存在的时间复杂度是 \(O(\log n^2)\),总时间复杂度就会达到 \(O(n^2 \log n^2)\),对于 \(n \le 5000\) 的数据范围不可接受。观察到值域比较小(\(-10^5 \le a_i \le 10^5\)),可以用一个桶存储二元组的和,那么单次查询的时间复杂度就可以达到 \(O(1)\),总时间复杂度为 \(O(n^2)\),可以通过本题。

小细节:由于 \(a_i\) 可能为负,将二元组的和装进桶里时,可以加一个偏移量(\(\ge 10^5\)),同时将桶的大小开到 \(4 \times 10^5\) 以上,以防止数组越界。

#include <bits/stdc++.h>
#define int long long
const int N = 5e3+10, V = 6e5+100, PLUS = 2e5+10;
int n, a[N], ans;
int buc[V];

signed main() {
  freopen("number.in", "r", stdin);
  freopen("number.out", "w", stdout);
  std::ios::sync_with_stdio(false); std::cin.tie(0);
  std::cin >> n;
  for (int i = 1; i <= n; i++) {
    std::cin >> a[i];
  }
  for (int i = 2; i <= n; i++) {
    for (int j = 2; j <= i; j++) {
      buc[a[i-1] + a[j-1] + PLUS]++;
    }
    int ok = false;
    for (int j = 1; j < i; j++) {
      if (buc[a[i] - a[j] + PLUS]) {
        ok = true;
        break;
      }
    }
    if (ok) {
      ans++;
    }
  }
  std::cout << ans << '\n';
  return 0;
}

T2

场上想了半天容斥,结果正解是 DP。谁懂我内心的痛。

我们定义 \(\operatorname{dp}(i, j, k)\) 表示:

  • 处理到第 \(i\) 个字符;
  • 当前匹配状态为 \(j\)
    • \(j = 0\):尚未匹配,等待匹配第一个 \(\texttt{S}\)
    • \(j = 1\):已匹配 \(\texttt{S}\),正等待匹配 \(\texttt{O}\)
    • \(j = 2\):已匹配 \(\texttt{SO}\),正等待匹配第二个 \(\texttt{S}\)
  • 已经匹配了 \(k\)\(\texttt{SOS} \ (0 \le k \le 3)\)

我们根据当前状态和下一个字符,决定下一个状态(具体看代码注释)。

虽然这道题没有卡空间,但是我还是使用滚动数组优化了空间复杂度。

#include <bits/stdc++.h>
#define int long long
const int N = 1e5+10, MOD = 1e9+7;
int dp[2][3][4], n, ans; 

signed main() {
  freopen("sos.in", "r", stdin);
  freopen("sos.out", "w", stdout);
  std::ios::sync_with_stdio(false); std::cin.tie(0);
  std::cin >> n;
  dp[0][0][0] = 1; // 初始化
  for (int ii = 1, i = 1; ii <= n; ii++, i ^= 1) {
    for (int j = 0; j <= 2; j++) {
      for (int k = 0; k <= 3; k++) {
        dp[i][j][k] = 0;
      }
    }
    for (int j = 0; j <= 2; j++) {
      for (int k = 0; k <= 3; k++) {
        if (dp[i^1][j][k] == 0) continue;
        if (j == 0) {
          // 状态 0:等待 'S'
          // 遇到 'S' -> 状态 1
          dp[i][1][k] = (dp[i][1][k] + dp[i^1][0][k]) % MOD;
          // 遇到其他 25 个字母 -> 状态 0
          dp[i][0][k] = (dp[i][0][k] + dp[i^1][0][k] * 25) % MOD;
        } else if (j == 1) {
          // 状态 1:等待 'O'
          // 遇到 'O' -> 状态 2
          dp[i][2][k] = (dp[i][2][k] + dp[i^1][1][k]) % MOD;
          // 遇到 'S' -> 状态 1
          dp[i][1][k] = (dp[i][1][k] + dp[i^1][1][k]) % MOD;
          // 遇到其他 24 个字母 -> 状态 0
          dp[i][0][k] = (dp[i][0][k] + dp[i^1][1][k] * 24) % MOD;
        } else {
          // 状态 2:已匹配 "SO",等待 'S'
          // 遇到 'S' -> 完成一个 "SOS",回到状态 0
          int newK = (k < 3) ? k + 1 : 3;
          dp[i][0][newK] = (dp[i][0][newK] + dp[i^1][2][k]) % MOD;
          // 遇到其他 25 个字母 -> 状态 0
          dp[i][0][k] = (dp[i][0][k] + dp[i^1][2][k] * 25) % MOD;
        }
      }
    }
  }
  for (int j = 0; j <= 2; j++) {
    ans = (ans + dp[n&1][j][3]) % MOD;
  }
  std::cout << ans << '\n';
  return 0;
}
posted @ 2025-11-24 14:53  JZ8  阅读(10)  评论(0)    收藏  举报