Processing math: 30%


ACL Beginner Contest F - Heights and Pairs

(9 mins to read)

有2n个人,身高分别为hi​,求将他们两两配对,且每对人身高均不同的方案数(mod 998244353)n5×104,hi105

设恰好有i对人身高不同的方案数为gi​,至少有i对人身高不同的方案数为fi恰好不好求,先求至少f_i = \sum \limits_{j=i}^n \binom{j}{i}g_j利用二项式反演g_i = \sum \limits_{j=i}^n(-1)^{j-i}\binom{j}{i}f_j答案即为g_0​单独考虑每种身高,若身高为h的有x个人,则将他们配成身高相同的i组人的方案数是\dfrac{\sum\limits_{j=1}^i \binom{x-2*j+2}{2}}{i!}这样对每种身高就可以写出一个OGF,全部乘起来后x^i的系数就是i组人配对的方案数,再考虑剩余的2n-2i个人,任意配对的方案数为\dfrac{(2n-2i)!}{(n-i)!2^{(n-i)}}​,两项相乘就是f_i​,再用上述式子即可得到g_0​其中n个OGF相乘利用分治NTT即可

在reverse一个空数组时会re,要注意

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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
#include <bits/stdc++.h>
using namespace std;
const int N = 4e5 + 5;
int n, h[N], cnt[N], d[N], sd[N];
int *f[N], buf[N<<5], *np(buf);
int fac[N], ifac[N], g[N];
const int mod = 998244353, G = 3;
int up, w[N], rev[N], inv[N];
int fpw(int a, int b)
{
int ans = 1;
while(b)
{
if(b&1) ans = 1ll*ans*a%mod;
a = 1ll*a*a%mod;
b >>= 1;
}
return ans;
}
void myassert(bool x)
{
if(!x) while(true);
}
namespace poly
{
void init(int n)
{
inv[0] = inv[1] = 1;
for(int i=2; i<=n; i++) inv[i] = 1ll*(mod-mod/i)*inv[mod%i]%mod;
up = 1; int l = 0;
while(up<=n) up <<= 1, l++;
for(int i=0; i<up; i++) rev[i] = (rev[i>>1]>>1)|((i&1)<<(l-1));
int wn = fpw(G, mod>>l); w[up>>1] = 1;
for(int i=(up>>1)+1; i<up; i++) w[i] = 1ll*w[i-1]*wn%mod;
for(int i=(up>>1)-1; i>=1; i--) w[i] = w[i<<1];
}
void clear(int *a, int n) { memset(a, 0, n<<2); }
int getlen(int n) { return 1<<(32-__builtin_clz(n)); }
inline void mul(int *a, int n, int x, int *b) { while(n--) *b++ = 1ll**a++*x%mod; }
inline void dot(int *a, int *b, int n, int *c) { while(n--) *c++ = 1ll**a++**b++%mod; }
void DFT(int *a, int l)
{
static unsigned long long tmp[N];
int u = __builtin_ctz(up/l), t;
for(int i=0; i<l; i++) tmp[i] = a[rev[i]>>u];
for(int i=1; i^l; i<<=1)
for(int j=0, d=i<<1; j^l; j+=d)
for(int k=0; k<i; k++)
t = tmp[i|j|k]*w[i|k]%mod, tmp[i|j|k] = tmp[j|k]+mod-t, tmp[j|k] += t;
for(int i=0; i<l; i++) a[i] = tmp[i]%mod;
}
void IDFT(int *a, int l)
{
reverse(a+1, a+l); DFT(a, l);
mul(a, l, mod-mod/l, a);
}
inline void conv(int *a, int *b, int l) { DFT(a, l); DFT(b, l); dot(a, b, l, a); IDFT(a, l); }
void mul(int *a, int n, int *b, int m, int *c)
{
static int l, ta[N], tb[N];
if(n+m==0) //此时l=1,IDFT会re!!
{
c[0] = 1ll*a[0]*b[0]%mod;
return;
}
l = getlen(n+m);
for(int i=0; i<=n; i++) ta[i] = a[i];
for(int i=0; i<=m; i++) tb[i] = b[i];
conv(ta, tb, l);
for(int i=0; i<=n+m; i++) c[i] = ta[i];
clear(ta, l), clear(tb, l);
}
}
void init(int n)
{
fac[0] = 1;
for(int i=1; i<=n; i++) fac[i] = 1ll*fac[i-1]*i%mod;
ifac[n] = fpw(fac[n], mod-2);
for(int i=n-1; i>=0; i--) ifac[i] = 1ll*ifac[i+1]*(i+1)%mod;
}
int comb(int a, int b)
{
if(a<b||b<0||a<0) return 0;
return 1ll*fac[a]*ifac[b]%mod*ifac[a-b]%mod;
}
void solve(int p, int l, int r)
{
f[p] = np, np += sd[r] - sd[l-1] + 1;
if(l==r)
{
int cur = 1;
for(int i=0; i<=d[l]; i++)
{
f[p][i] = cur;
cur = 1ll*cur*comb(cnt[l]-2*i, 2)%mod*inv[i+1]%mod;
}
return;
}
int mid = (l+r)>>1;
solve(p<<1, l, mid); solve(p<<1|1, mid+1, r);
poly::mul(f[p<<1], sd[mid]-sd[l-1], f[p<<1|1], sd[r]-sd[mid], f[p]);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
int mx = 0;
init(2*n);
poly::init(n);
for(int i=1; i<=2*n; i++)
{
cin >> h[i];
cnt[h[i]]++;
mx = max(mx, h[i]);
}
for(int i=1; i<=mx; i++)
{
d[i] = cnt[i]/2;
sd[i] = sd[i-1] + d[i];
}
solve(1, 1, mx);
for(int i=0; i<=sd[mx]; i++) g[i] = 1ll*f[1][i]*fac[2*n-2*i]%mod*ifac[n-i]%mod*fpw(fpw(2, n-i), mod-2)%mod;
int ans = 0;
for(int i=0; i<=sd[mx]; i++)
{
if(i&1) ans = (ans - g[i] + mod)%mod;
else ans = (ans + g[i])%mod;
}
cout << ans << '\n';
return 0;
}