题解 CF1808D【Petya, Petya, Petr, and Palindromes】

2023-04-02 20:02:49


分析

此题最重要的性质是在于 $k$ 的范围:保证是奇数。$k$ 是奇数,那么所有长度为 $k$ 的区间当中的所 有元素,轴对称的位置与其本身的位置 奇偶性保持一致。因为假设对称轴坐标是整数 $m$,那么对称轴左侧位置为 $m-i$ 的元素,对称之后就到了 $m+i$ 的位置,而 $(m+i) - (m-i) = 2i$ 是偶数。

对于此类计数问题,可以从左到右,依次扫描每个元素,计算有多少元素可以与之配对。为了避免重复,对于第 $p$ 个元素,我们只从 $[1, p-1]$ 范围内(即 它的左侧)进行计数。

长度为 $n$ 的序列最多有 $n-k+1$ 个长度为 $k$ 的子区间,每个子区间最多贡献 $\frac{k+1}{2}$ 的答案(要记得 $k$ 是奇数),因此答案的最大值是 $(n-k+1) \times \frac{k+1}{2}$。

假设对于第 $p$ 个元素,它左侧可能存在与之配对(对称并相等)的元素的范围是 $[l, r]$,那么只要提前分奇偶把所有数字出现的位置存下来,使用 std::upper_boundstd::lower_bound 就可以快速计算出这个范围内有多少个。答案的最大值减去所有的配对个数,就是最终答案。

那么接下来的主要问题,就是计算 $[l, r]$。

对于 $l$

  • 首先要保证 $l$ 到 $p$ 的距离在 $k$ 以内,即 $p-l+1 \leq k$,化简得到 $l \geq p-k+1$。
  • $l$ 与 $p$ 之间对称轴的位置是 $m=\frac{l+p}{2}$,要保证对称轴左边还有足够的位置来容纳这个长度为 $k$ 的区间,得到 $\frac{l+p}{2} - \frac{k+1}{2} \geq 1$,化简得到 $l \geq k+2-p-1$。

对于 $r$

  • 为了避免重复计算,我们只考虑在 $p$ 左边的可能的位置。$r$ 的最大值在对称轴位于 $m=p-1$ 的时候取到,此时 $r=p-2$。因此:$r \leq p - 2$。
  • $r$ 与 $p$ 之间对称轴的位置是 $m = \frac{r+p}{2}$,与刚才类似,现在要保证对称轴右边(到数组尽头)还有足够的位置来容纳这个长度为 $k$ 的区间,即 $\frac{r+p}{2} + \frac{k+1}{2} <= n$,化简得到 $r \leq 2n - p - k + 1$。

整理式子

根据上述计算,有 $$\begin{aligned} l &= \max(p-k+1, k-p+1) \\ r &= \min(p-2, 2n-p-k+1) \end{aligned}$$ 要注意有可能计算得到 $l > r$,这种情况应当予以忽略。

代码

void solution() {
  int n, k;
  std::cin >> n >> k;
  assert(k & 1);
  std::vector<int> a(n + 1);
  for (int i = 1; i <= n; ++i) std::cin >> a[i];
  std::vector<std::vector<int>> odd(200001), even(200001);
  for (int i = 1; i <= n; ++i)
    i & 1 ? odd[a[i]].push_back(i) : even[a[i]].push_back(i);
  ll ans = 1ll * (k / 2) * (n - k + 1);
  auto solve = [&](auto& pos) {
    for (int x = 1; x <= 200000; ++x) {
      for (auto p : pos[x]) {
        int l = std::max(p - k + 1, k - p + 1);
        int r = std::min(p - 2, 2 * n - p - k + 1);
        auto it1 = std::lower_bound(pos[x].begin(), pos[x].end(), l);
        auto it2 = std::upper_bound(pos[x].begin(), pos[x].end(), r);
        auto sub = std::distance(it1, it2);
        ans -= sub <= 0 ? 0 : sub;
      }
    }
  };
  solve(odd);
  solve(even);
  std::cout << ans << "\n";
}