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\) 再暴力拓展即可。
-
如果 \(i\leq r\),则说明 \(i\) 一定在 \([x-len_x,x+len_x]\) 内。找到 \(i\) 关于 \(x\) 的对称点 \(j=2x-i\)。
-
如果 \(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\)。
-
如果 \(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;
}