「CF1930E」2..3...4.... Wonderful! Wonderful!

Description

Link:CF1930E

有一个长度为 nn 的数组 aa,初始满足 ai=ia_i = i。对于一个参数 kk (1≤k≤⌊n−12⌋1 \leq k \leq \lfloor \frac{n - 1}{2} \rfloor),你可以对 aa 进行任意次数(可能为 00 次)的以下操作:

  • 从 aa 中选择一个长度为 2k+12k + 1 的子序列,然后删除其中的前 kk 个元素和后 kk 个元素。

求对于每个 kk (1≤k≤⌊n−12⌋1 \leq k \leq \lfloor \frac{n - 1}{2} \rfloor),最终可以得到多少个不同的数组 aa。

数据范围:3≤n≤1063 \leq n \leq 10^6。

时空限制:33s / 256256MiB

Solution

对于一种可能的局面,我们将已经被删除的元素标记成 11,没有被删除的元素标记成 00。那么有一个简单的必要条件:

  • 11 的个数是 2k2k 的倍数。
  • 至少存在一个 00,使得其左边和右边各自至少有 kk 个 11。

然后发现这也是充分的:先找到一个 00 作为操作中心,满足前后各自至少有 kk 个 11。将其视为最后一次操作,那么考虑退回这次操作。先将这个 00 前后的 k−1k - 1 个 11 还原成 00,此时不妨设左边 11 的数量大于右边 11 的数量,将右边第 kk 个 11 还原成 00,此时全局应有奇数个 11,选取最中间的那个 11 还原成 00(这个 11 位于左侧,符合要求),然后将这个 00 作为倒二次的操作中心,依此类推。

考虑计数,假设选的参数为 kk,一共删了 xx 个数(xx 为 kk 的倍数)。正难则反,考虑使用总方案数减去不合法的方案数。

可以先将这 xx 个 11 摆好,然后将剩下的 00 插入进去。要想使得其不合法,00 只能插在前 kk 个 11 的左侧或后 kk 个 11 的右侧,插在中间的空隙是不行的。将 n−xn - x 个 00 分成 2k2k 个组是一个简单的插板法,于是贡献为

(nx)−(n−x+2k−12k−1)\binom{n}{x} - \binom{n - x + 2k - 1}{2k - 1}

时间复杂度 O(nlog⁡n)\mathcal{O}(n \log n),瓶颈在于调和级数式枚举。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#include <bits/stdc++.h>

using i64 = long long;

#define debug(a) std::cout << #a << '=' << (a) << ' '

template <class T>
inline void chmin(T &x, const T &y) {
if (x > y) {
x = y;
}
}
template <class T>
inline void chmax(T &x, const T &y) {
if (x < y) {
x = y;
}
}

const int mod = 998244353;

inline void add(int &x, const int &y) {
x += y; if (x >= mod) x -= mod;
}
inline void dec(int &x, const int &y) {
x -= y; if (x < 0) x += mod;
}

template <class T>
constexpr int qpow(int a, T b, int p) {
int ans = 1;
for (; b; b >>= 1) {
if (b & 1) ans = 1ll * ans * a % p;
a = 1ll * a * a % p;
}
return ans;
}

const int N = 1000100;

int n;

struct BinomCoef {
std::vector<int> fact, facv;

BinomCoef() {}
BinomCoef(int n) {
init(n);
}

void init(const int &n) {
fact.resize(n + 1), facv.resize(n + 1);

fact[0] = 1;
for (int i = 1; i <= n; i ++) {
fact[i] = 1ll * fact[i - 1] * i % mod;
}
facv[n] = qpow(fact[n], mod - 2, mod);
for (int i = n - 1; i >= 0; i --) {
facv[i] = 1ll * facv[i + 1] * (i + 1) % mod;
}
}

int binom(int n, int m) {
if (n < m || m < 0) {
return 0;
}
return 1ll * facv[m] * facv[n - m] % mod * fact[n] % mod;
}
};

void work() {
std::cin >> n;

BinomCoef bc(n * 2);

for (int k = 1; k <= (n - 1) / 2; k ++) {
int ans = 1;
for (int x = 2 * k; x < n; x += 2 * k) {
add(ans, bc.binom(n, x));
dec(ans, bc.binom(n - x + 2 * k - 1, 2 * k - 1));
}
std::cout << ans << ' ';
}
std::cout << '\n';
}

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

int T;
std::cin >> T;

while (T --) {
work();
}

return 0;
}