AT_nikkei2019_2_final_h 逆にする関数 题解

Description

给定一个由 \(1, 2, \dots, m\) 组成的数列 \((v_1, v_2, \dots, v_k)\) 和一个函数 \(f\colon \{1, \dots, m\} \to \{1, \dots, m\}\),当 \(f\) 满足以下条件时,称 \(f\)逆转数列 \((v_1, v_2, \dots, v_k)\)

  • 数列 \((f(v_1), f(v_2), \dots, f(v_k))\) 与原数列反转后的数列 \((v_k, v_{k-1}, \dots, v_1)\) 完全相同。

现给定一个由 \(1, 2, \dots, m\) 组成的数列 \((a_1, a_2, \dots, a_n)\)。该数列的所有非空连续子序列共有 \(\frac{n(n+1)}{2}\) 种。请你统计,对于所有这些连续子序列,能够逆转它们的函数 \(f\) 的种数之和,并输出其对 \(998244353\) 取模的结果。

\(1\leq n,m\leq 3\times 10^5\)

Solution

首先显然是对于每个子区间算贡献,这个子区间 \([l,r]\) 合法当且仅当所有 \(a_i=a_j,i\neq j\),都满足 \(a_{l+r-i}=a_{l+r-j}\),其贡献为 \(m^{m-区间颜色数量}\)

将数组中每两个相邻的位置之间插入一个 \(0\) 后,记录 \(pre_i\) 表示 \(i\) 左边第一个等于 \(a_i\) 的位置,\(nxt_i\) 表示 \(i\) 右边第一个等于 \(i\) 的位置,再枚举区间的中心,暴力拓展的过程中用 \(pre\)\(nxt\) 维护颜色数量即可做到 \(O(n^2)\)

考虑优化。

注意到这个东西和回文有很大的相似处,而不用哈希的话回文只能用 manacher 做,所以这里也考虑 manacher 算法。

类似 manacher 的思想,从左往右枚举中心 \(i\),求出以当前位置为中心的最长合法区间向右拓展的长度 \(len_i\)\([i-len_i,i+len_i]\) 的颜色数量 \(cnt_i\),以及以 \(i\) 为中心的所有合法区间的贡献和。

假设当前已确定的右端点最大的中心是 \(x\),右端点为 \(r\),显然 \(r=x+len_x\)

如果 \(i>r\),将 \(len_i\) 设为 \(0\) 再暴力拓展即可。

  1. 如果 \(i\leq r\),则说明 \(i\) 一定在 \([x-len_x,x+len_x]\) 内。找到 \(i\) 关于 \(x\) 的对称点 \(j=2x-i\)

  2. 如果 \(i+len_j<x+len_x\),则说明 \(len_i=len_j\),因为 \([x-len_x,x+len_x]\) 合法,所以 \([2i-r,r]\)\([x-len_x,2j-(x-len_x)]\) 中的值存在双射关系,合法性也一样,而 \(j\) 无法跳出 \(x\) 的区间,所以 \(len_i=len_j\)

  3. 如果 \(i+len-j\geq x+len_x\),说明 \(len_i\geq x-i\),这里 \(i\)\(j\) 在区间外的东西不能继承,所以只能先将 \(j\)\(len\) 缩减到 \(r-i\),此时 \(i\) 继承 \(j\) 后再暴力拓展。

第三部分对于单个 \(i\) 的复杂度是 \(O(i+len_{2x-i}-r)\),但是可以证明总复杂度是 \(O(n)\) 的。

证明

\(k=i-x\),则 \(i-2k+len_{2x-i}\leq x+len_x\),所以 \(len_{2x-i}-len_{x}\leq k\)

由于求出 \(i\)\(x\) 会更新成 \(i\),所以我们用 \(O(i+len_{2x-i}-x-len_x)=O(k)\) 的代价将中心向右移动了 \(k\),总复杂度也就是 \(O(n)\)

时间复杂度:\(O(n)\)

Code

#include <bits/stdc++.h>

// #define int int64_t

const int kMaxN = 4e6 + 5, kMod = 998244353;

int n, m;
int a[kMaxN], pw[kMaxN], pre[kMaxN], nxt[kMaxN];
int len[kMaxN], sum[kMaxN], cnt[kMaxN];

int qpow(int bs, int64_t idx = kMod - 2) {
  int ret = 1;
  for (; idx; idx >>= 1, bs = (int64_t)bs * bs % kMod)
    if (idx & 1)
      ret = (int64_t)ret * bs % kMod;
  return ret;
}

inline int add(int x, int y) { return (x + y >= kMod ? x + y - kMod : x + y); }
inline int sub(int x, int y) { return (x >= y ? x - y : x - y + kMod); }
inline void inc(int &x, int y) { (x += y) >= kMod ? x -= kMod : x; }
inline void dec(int &x, int y) { (x -= y) < 0 ? x += kMod : x; }

void prework() {
  pw[0] = 1;
  for (int i = 1; i <= m + 1; ++i) pw[i] = 1ll * m * pw[i - 1] % kMod;
  static int lst[kMaxN];
  std::fill_n(lst, m + 1, 0);
  for (int i = 1; i <= n; ++i) pre[i] = lst[a[i]], lst[a[i]] = i;
  std::fill_n(lst, m + 1, n + 1);
  for (int i = n; i; --i) nxt[i] = lst[a[i]], lst[a[i]] = i;
}

void dickdreamer() {
  std::cin >> n >> m;
  n = 2 * n + 1;
  for (int i = 2; i <= n; i += 2) std::cin >> a[i];
  prework();
  int x = 0, r = 0;
  for (int i = 1; i <= n; ++i) {
    if (i > r) {
      len[i] = 0, cnt[i] = m - (a[i] != 0), sum[i] = (a[i] != 0) * pw[m - 1];
      for (; i - len[i] - 1 >= 1 && i + len[i] + 1 <= n;) {
        int p1 = i - len[i] - 1, p2 = i + len[i] + 1;
        if (nxt[p1] <= i + len[i] && a[2 * i - nxt[p1]] != a[p2]) break;
        if (pre[p2] >= i - len[i] && a[2 * i - pre[p2]] != a[p1]) break;
        if (a[p1] && nxt[p1] > i + len[i]) --cnt[i];
        if (a[p2] && pre[p2] < i - len[i] - 1) --cnt[i];
        ++len[i], inc(sum[i], (a[p1] != 0) * pw[cnt[i]]);
      }
      x = i, r = i + len[i];
    } else if (i + len[2 * x - i] < r) {
      len[i] = len[2 * x - i], sum[i] = sum[2 * x - i], cnt[i] = cnt[2 * x - i];
    } else {
      {
        int j = 2 * x - i, len = ::len[j], sum = ::sum[j], cnt = ::cnt[j];
        for (; i + len > r;) {
          int p1 = j - len, p2 = j + len;
          --len, dec(sum, (a[p1] != 0) * pw[cnt]);
          if (a[p1] && nxt[p1] > j + len) ++cnt;
          if (a[p2] && pre[p2] < j - len - 1) ++cnt;
        }
        ::len[i] = len, ::sum[i] = sum, ::cnt[i] = cnt;
      }
      assert(i + len[i] == r);
      for (; i - len[i] - 1 >= 1 && i + len[i] + 1 <= n;) {
        int p1 = i - len[i] - 1, p2 = i + len[i] + 1;
        if (nxt[p1] <= i + len[i] && a[2 * i - nxt[p1]] != a[p2]) break;
        if (pre[p2] >= i - len[i] && a[2 * i - pre[p2]] != a[p1]) break;
        if (a[p1] && nxt[p1] > i + len[i]) --cnt[i];
        if (a[p2] && pre[p2] < i - len[i] - 1) --cnt[i];
        ++len[i], inc(sum[i], (a[p1] != 0) * pw[cnt[i]]);
      }
      x = i, r = i + len[i];
    }
  }
  int ans = 0;
  for (int i = 1; i <= n; ++i) inc(ans, sum[i]);
  std::cout << ans << '\n';
}

int32_t main() {
#ifdef ORZXKR
  freopen("in.txt", "r", stdin);
  freopen("out.txt", "w", stdout);
#endif
  std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
  int T = 1;
  // std::cin >> T;
  while (T--) dickdreamer();
  // std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
  return 0;
}
posted @ 2025-10-20 21:15  下蛋爷  阅读(15)  评论(0)    收藏  举报