「雅礼集训 2018 Day4」Magic

#6503. 「雅礼集训 2018 Day4」Magic

Summarize

题目概括征集中~

Solution

由于题意中本质不同的方案不是特别好处理,我们可以在每张卡片上标号,将计算出的答案乘上 $\prod_{i=1}^m\frac{1}{a_i!}$ 即可。

考虑容斥。记 $f[i]$ 为至少有 $i$ 个魔法对的方案数,则答案为 $\sum_{i=k}^n(-1)^{i-k}\times{i\choose k}\times f[i]$。

注意:「TJOI2019」唱、跳、rap 和篮球 即为本题 $k=0$ 的特例。

考虑如何计算 $f[i]$。设第一种颜色至少有 $i_1$ 个魔法对,第二种颜色至少有 $i_2$ 个魔法对……以此类推。考虑以下构造序列的方法:第 $j$ 种颜色保留 $i_j$ 张卡片作为预留卡片,将剩余的卡片任意排列,然后依次将所有预留的卡片插入到原序列中与之颜色相同的卡片的后面。不难发现,按照该方法构造序列的方案数为 $(n-k)!\times\prod_{j=1}^m{a_j\choose i_j}\times \frac{(a_j-1)!}{(a_j-i_j-1)!}$,并且该序列中至少有 $\sum_{j=1}^mi_j$ 个魔法对。

因此,得到 $f[i]$ 的计算方法:
$$
f[i]=\sum_{i_1=0}^{a_1-1}\sum_{i_2=0}^{a_2-1}…\sum_{i_m=0}^{a_m-1}[i_1+i_2+…+i_m=i] (n-i)!\times\prod_{j=1}^m{a_j\choose i_j}\times \frac{(a_j-1)!}{(a_j-i_j-1)!}
$$
注意到这是一个 $m$ 维卷积的形式,但是如果用暴力生成函数 + NTT 计算,复杂度为 $O(mn\log n)$,不能通过本题。我们可以用类似启发式合并的策略,交换多项式乘法运算顺序,每次将两个阶数最小的多项式相乘,即可将复杂度控制在 $O(m\log^2 n)$。

Code

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
101
102
103
104
105
106
107
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int MAXN = 100005, MAXM = 20005;
const ll MOD = 998244353, G = 3;

inline ll qpow(ll a, ll k) {
ll ret = 1;
while (k) {
if (k & 1) ret = ret * a % MOD;
a = a * a % MOD;
k >>= 1;
}
return ret;
}

namespace FFT {
int n, r[MAXN << 1];
void NTT(ll *a, int op) {
int k = 0;
for (; (1 << k) < n; ++k);
for (int i = 0; i < n; ++i) {
r[i] = r[i >> 1] >> 1 | (i & 1) << (k - 1);
if (i < r[i]) swap(a[i], a[r[i]]);
}
for (int l = 2; l <= n; l <<= 1) {
int m = l >> 1;
ll w = qpow(G, (MOD - 1) / l);
if (op == -1) w = qpow(w, MOD - 2);
for (int i = 0; i < n; i += l) {
ll wk = 1;
for (int j = 0; j < m; ++j, wk = wk * w % MOD) {
ll p = a[i + j], q = wk * a[i + j + m] % MOD;
a[i + j] = (p + q) % MOD;
a[i + j + m] = (p - q + MOD) % MOD;
}
}
}
}
void DFT(ll *a) {
NTT(a, 1);
}
void IDFT(ll *a) {
NTT(a, -1);
ll inv = qpow(n, MOD - 2);
for (int i = 0; i < n; ++i)
a[i] = a[i] * inv % MOD;
}
}

int m, n, k;
int a[MAXM];
vector<ll> b[MAXM << 1];
priority_queue<pair<int, int> > q;
ll x[MAXN << 1], y[MAXN << 1], ans;
ll fac[MAXN], inv[MAXN], facinv[MAXN];

inline ll C(int n, int k) {
return fac[n] * facinv[k] % MOD * facinv[n - k] % MOD;
}

int main() {
scanf("%d%d%d", &m, &n, &k);
fac[0] = facinv[0] = 1;
fac[1] = inv[1] = facinv[1] = 1;
for (int i = 2; i <= n; ++i) {
fac[i] = fac[i - 1] * i % MOD;
inv[i] = (MOD - MOD / i) * inv[MOD % i] % MOD;
facinv[i] = facinv[i - 1] * inv[i] % MOD;
}
for (int i = 1; i <= m; ++i) {
scanf("%d", &a[i]);
for (int j = 0; j < a[i]; ++j)
b[i].push_back(C(a[i], j) * fac[a[i] - 1] % MOD * facinv[a[i] - j - 1] % MOD);
q.push(make_pair(-b[i].size(), i));
}
int cnt = m;
while (q.size() > 1) {
int id1 = q.top().second, sz1 = -q.top().first;
q.pop();
int id2 = q.top().second, sz2 = -q.top().first;
q.pop();
FFT::n = 1;
while (FFT::n <= sz1 + sz2) FFT::n <<= 1;
for (int i = 0; i <= FFT::n; ++i) x[i] = y[i] = 0;
for (int i = 0; i < sz1; ++i) x[i] = b[id1][i];
for (int i = 0; i < sz2; ++i) y[i] = b[id2][i];
FFT::DFT(x), FFT::DFT(y);
for (int i = 0; i <= FFT::n; ++i) x[i] = x[i] * y[i] % MOD;
FFT::IDFT(x);
cnt++;
for (int i = 0; i <= sz1 + sz2 - 2; ++i)
b[cnt].push_back(x[i]);
q.push(make_pair(-b[cnt].size(), cnt));
}
int id = q.top().second, sz = -q.top().first;
for (int i = k; i < sz; ++i) {
ll tmp = C(i, k) * fac[n - i] % MOD * b[id][i] % MOD;
if ((i - k) & 1) ans = (ans - tmp + MOD) % MOD;
else ans = (ans + tmp) % MOD;
}
for (int i = 1; i <= m; ++i)
ans = ans * qpow(fac[a[i]], MOD - 2) % MOD;
printf("%lld", ans);
return 0;
}

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×