A

直接统计 个骰子的结果为 的个数与 比大小即可。

#include <bits/stdc++.h>

int main() {
  
  int t;
  std::cin >> t;
  while (t--) {
    std::vector<int> a(12);
    for (int i = 0; i < 12; i++) {
      std::cin >> a[i];
    }
    int x, y;
    std::cin >> x >> y;
    int cnt = 0;
    for (int i = 0; i < 12; i++) {
      if (a[i] == y) {
        cnt++;
      }
    }
    std::cout << (cnt < x ? "win" : "lose") << '\n';
  }
  
  return 0;
}

B

注意到 ,以及 只有两种 两种取值,记 为序列中 的个数, 为序列中 的个数。

只可能出现三种情况:

  • 可选的个数超过 的一半。
  • 可选的个数足够 个。
  • :否则中位数还是 ,然后 也没法不选。

#include <bits/stdc++.h>

int main() {

  int t;
  std::cin >> t;
  while (t--) {
    int n, k;
    std::cin >> n >> k;
    int one = 0;
    for (int i = 0; i < n; i++) {
      int x;
      std::cin >> x;
      if (x == 1) {
          one += 1;
      }
    }
    if ((n - one >= k) || one > k / 2) {
      std::cout << 0 << '\n';
    } else {
      std::cout << 1 << '\n';
    }
  }

  return 0;
}

C

记初始序列中正数个数为 ,负数个数为 ,零的个数为

注意两个地方:

  1. 只能消除正数但不能增加正数。
  2. 负数可以增加正数也可以消除正数, 个负数最多变成 个正数。

#include <bits/stdc++.h>

int main() {
  int t;
  std::cin >> t;
  while (t--) {
    int n, m;
    std::cin >> n >> m;
    int pos = 0, neg = 0, zero = 0;
    for (int i = 0; i < n; i++) {
      int x;
      std::cin >> x;
      if (x > 0) {
        pos += 1;
      } else if (x < 0) {
        neg += 1;
      } else {
        zero += 1;
      }
    }
    int ans = std::abs(pos - m);
    if ((pos > m && !zero && !neg) || (pos < m && pos + neg - 1 < m)) {
      ans = -1;
    }
    std::cout << ans << '\n';
  }

  return 0;
}

D

  1. 对于任意一个从 的操作序列来说,一定可以通过调整操作 的顺序先使用操作 后使用操作 ,使得操作次数不变,且仍然可以让
  2. 在最优操作中, 操作至多使用一次。

证明:

表示操作 表示操作

考虑这样的一个操作序列:,其中 分别表示前缀操作序列和后缀操作序列。

做完前缀操作序列 之后的值为 ,操作 选的那个数为

我们交换两个操作的顺序,先做操作 再做操作 ,令

我么只要证明存在一个 ,使得 即可,不难注意到 即可。

所以我们证明了对于任意一个相邻的操作,如果 的前面,一定可以交换顺序,且交换之后操作完得到的数还是一样的,同时后缀 的操作序列没变,所以最后得到的结果还是 ,且操作总次数没变多。

故任意一种可行的序列一定可以通过交换顺序变成

然后我们在注意到连续的操作 是没意义的,因为只要令 ,那么这一段连续的操作 可以被合并为一段。

综上最优操作序列一定只有两种情况: 或者

所以可以枚举操作 的操作次数,然后判断是否还需要一次操作 去让

时间复杂度

#include <bits/stdc++.h>

int main() {
  int t;
  std::cin >> t;
  while (t--) {
    long long x, y;
    std::cin >> x >> y;
    int ans = 0;
    while ((x & y) != x) {
      ans++;
      x >>= 1;
    }
    std::cout << ans + (x != y) << '\n';
  }

  return 0;
}

E

做法一:预处理前后缀

子序列的匹配是贪心的,对于 来说,他一定是在前缀 从左到右找某些项匹配,在后缀 从右往左找某些项拼接起来,所以考虑计算两个数组

表示满足 的子序列的 的最大值。

表示满足 的子序列的 的最大值。

答案即为

显然处理 与计算答案复杂度都是 的。

#include <bits/stdc++.h>

int main() {
  
  int n;
  std::cin >> n;
  std::vector<int> P(n + 1);
  for (int i = 1; i <= n; i++) {
    std::cin >> P[i];
  }
  std::vector<int> lsz(n + 1), rsz(n + 1);
  for (int i = 2; i < n; i++) {
    int jl = 1, jr = n;
    while (jl < i && i + lsz[i] <= n) {
      if (P[jl] == P[i + lsz[i]]) {
        lsz[i]++;
      }
      jl++;
    }
    while (jr > i && i - rsz[i] >= 1) {
      if (P[jr] == P[i - rsz[i]]) {
        rsz[i]++;
      }
      jr--;
    }
  }
  int ans = 0;
  for (int i = 2; i < n; i++) {
    for (int j = i; j < n; j++) {
      ans += lsz[i] + rsz[j] >= j - i + 1;
    }
  }
  std::cout << ans << '\n';

  return 0;
}

做法二:二分

首先要注意到:

对于 ,若 的子序列,那么一个更小的区间 ,也必然满足 的子序列。

基于这个性质,我么可以枚举 ,通过二分出最大的一个 满足 的子序列,那么对于 都是满足条件的。

时间复杂度

#include <bits/stdc++.h>

int main() {
  
  int n;
  std::cin >> n;
  std::vector<int> P(n + 1);
  for (int i = 1; i <= n; i++) {
    std::cin >> P[i];
  }
  int ans = 0;
  auto check = [&](int l, int r) {
    int idx = l;
    for (int i = 1; i <= n; i++) {
      if (i == l) {
        i = r;
        continue;
      }
      if (idx <= r && P[idx] == P[i]) {
        idx++;
      }
    }
    return idx == r + 1;
  };
  for (int i = 2; i < n; i++) {
    int l = i - 1, r = n - 1;
    while (l < r) {
      int mid = (l + r + 1) >> 1;
      if (check(i, mid)) {
        l = mid;
      } else {
        r = mid - 1;
      }
    }
    ans += l - i + 1;
  }
  std::cout << ans << '\n';

  return 0;
}

做法三:双指针

同样注意到上面的那个性质,不难发现对于 如果存在满足条件的 ,那么 随着 的增加是单调不降的,所以可以用双指针。

时间复杂度

#include <bits/stdc++.h>

int main() {
  
  int n;
  std::cin >> n;
  std::vector<int> P(n + 1);
  for (int i = 1; i <= n; i++) {
    std::cin >> P[i];
  }
  int ans = 0;
  auto check = [&](int l, int r) {
    int idx = l;
    for (int i = 1; i <= n; i++) {
      if (i == l) {
        i = r;
        continue;
      }
      if (idx <= r && P[idx] == P[i]) {
        idx++;
      }
    }
    return idx == r + 1;
  };
  for (int i = 2, j = 2; i < n; i++) {
    j = std::max(j, i);
    while (j < n && check(i, j)) {
      j++;
    }
    ans += j - i;
  }
  std::cout << ans << '\n';

  return 0;
}

F

先考虑暴力 DP 怎么做。

表示考虑了前 个位置,若 为奇数,则 表示第 个位置 的值;若 为偶数,则 表示 的值的方案数。

枚举 与当前位置 的取值 去转移:

  • 为奇数,若满足 ,则
  • 为偶数,则

这样的 DP 复杂度是 的,显然无法通过。

考虑能否优化掉转移枚举

首先 为偶数的情况是好优化的:

  • ,那么对于 来说, 形成的是一个连续的区间
  • ,那么对于 来说, 可以形成两个连续的区间:

那么我么需要对这些区间的 加上 ,这个过程显然可以通过差分优化到

对于 为奇数的情况:

首先需要先注意到对于给定的 ,所有满足 至多形成 个互不重叠的连续区间。

考虑前缀和思想,我么可以分两部做:

  1. 对满足 的所有 都加上
  2. 对满足 的所有 都减去

所以,我们可以先求出对于固定的 ,满足 形成的 个互不重叠的连续区间。(这个性质可以从经典的 字典树去想,考虑从二进制高位到低位去确定可能的 的范围)

这个过程可以用 的复杂度算出来,那么再结合 的限制 ,我么可以得到最终满足条件的 个区间:

此时需要对这些区间的 加上 ,这个过程也可以用差分优化,同理处理 也一样。

综上对于 为奇偶的情况都可以用差分优化,所以 DP 的时间复杂度可以降为

最后答案即为 ,故总的时间复杂度

#include <bits/stdc++.h>

constexpr int mod = 1e9 + 7;
constexpr int MAX = 1 << 10;
constexpr int MAX_BIT = 10;

void Add(int& x, int y) {
  x += y;
  if (x >= mod) {
    x -= mod;
  }
  if (x < 0) {
    x += mod;
  }
}

std::vector<std::array<int, 2>> GetSegXOR(int x, int y) {
  std::vector<std::array<int, 2>> seg;
  if (y < 0) {
    return seg;
  }
  int p = 0;
  for (int i = MAX_BIT - 1; i >= 0; i--) {
    if (y >> i & 1) {
      if (x >> i & 1) {
        seg.push_back({p | (1 << i), p | ((1 << (i + 1)) - 1)});
      } else {
        seg.push_back({p, p | ((1 << i) - 1)});
        p |= 1 << i;
      }
    } else {
      if (x >> i & 1) {
        p |= 1 << i;
      }
    }
  }
  assert((p ^ x) == y);
  seg.push_back({p, p});
  return seg;
}

void Solve() {
  int n;
  std::cin >> n;
  std::vector<int> A(n + 1), L(n + 1), R(n + 1);
  for (int i = 1; i <= n; i++) {
    std::cin >> A[i] >> L[i] >> R[i];
  }
  std::vector<int> dp(MAX);
  for (int i = 0; i <= A[1]; i++) {
      Add(dp[i], 1);
  }
  for (int i = 2; i <= n; i++) {
    std::vector<int> ndp(MAX);
    for (int j = 0; j < MAX; j++) {
      if (dp[j] == 0) {
        continue;
      }
      if (i % 2 == 0) {
        if (j >= A[i]) {
          Add(ndp[j - A[i]], dp[j]);
          if (j + 1 < MAX) {
            Add(ndp[j + 1], -dp[j]);
          }
        } else {
          Add(ndp[0], dp[j]);
          if (j + 1 < MAX) {
            Add(ndp[j + 1], -dp[j]);
          }
          Add(ndp[1], dp[j]);
          if (A[i] - j + 1 < MAX) {
            Add(ndp[A[i] - j + 1], -dp[j]);
          }
        }
      } else {
        auto segl = GetSegXOR(j, L[i - 1] - 1);
        auto segr = GetSegXOR(j, R[i - 1]);
        for (auto& [l, r] : segr) {
          l = std::max(0, l);
          r = std::min(r, A[i]);
          if (l > r) {
            continue;
          }
          Add(ndp[l], dp[j]);
          if (r + 1 < MAX) {
            Add(ndp[r + 1], -dp[j]);
          }
        }
        for (auto& [l, r] : segl) {
          l = std::max(0, l);
          r = std::min(r, A[i]);
          if (l > r) {
            continue;
          }
          Add(ndp[l], -dp[j]);
          if (r + 1 < MAX) {
            Add(ndp[r + 1], dp[j]);
          }
        }
      }
    }
    for (int j = 1; j < MAX; j++) {
      Add(ndp[j], ndp[j - 1]);
    }
    dp.swap(ndp);
  }
  int ans = 0;
  for (int i = 0; i < MAX; i++) {
    Add(ans, dp[i]);
  }
  std::cout << ans << '\n';
}

int main() {
  std::ios::sync_with_stdio(false);
  std::cin.tie(nullptr);

  int t;
  std::cin >> t;
  while (t--) {
    Solve();
  }

  return 0;
}