【题解】P4931 [MtOI2018] 情侣?给我烧了!(加强版)
不算堂堂的复活
原题链接 P4921 [MtOI2018] 情侣?给我烧了!
思路
推导 / 二项式反演 + 生成函数
这个题看到恰好 \(k\) 对其实很容易想到二项式反演,但是如果要推反演就需要很复杂的 GF 来简化,直接上简单的推导做法好了。
考虑分化一下问题,分成钦定恰好 \(k\) 对配对的情侣和 \(n - k\) 对错排的情侣。
钦定 \(k\) 对配对的情侣:
-
钦定配对的情侣,有 \({n \choose k}\) 种选法。
-
钦定 \(k\) 对座位,有 \({n \choose k}\) 种选法。
-
情侣之间可以左右交换位置,有 \(2^k\) 种情况。
-
每对情侣都可以任意选一对座位做,有 \(k!\) 种每对情侣和每对座位的配对情况。
这部分对答案的贡献是 \(({n \choose k})^2 \cdot 2^k \cdot (k!)\).
然后考虑剩下的 \(n - k\) 对错排的情侣,令 \(g[n]\) 表示 \(n\) 对情侣错排的情况。
类似经典错排的推导方式,首先钦定一对错排的情侣坐在编号最小的一对座位,这里有 \(2n (2n - 2)\) 种选择两个人的方式。
考虑被钦定的这两个人所对应的伴侣。
如果他们坐在一排则转化成 \(n - 2\) 对情侣的错排问题。考虑这两个人的座位共有 \(n - 1\) 种情况,且这两个人可以左右交换位置,所以贡献是 \(2 (n - 1) \cdot g[n - 2]\);
反之,相当于钦定这两个人需要错排,相当于将这两个人配对成一对情侣再做错排,贡献为 \(g[n - 1]\).
所以得到 \(g\) 的递推式是 \(g[n] = 2 (n - 1) \cdot g[n - 2] + g[n - 1]\).
所以答案是 \(({n \choose k})^2 \cdot 2^k \cdot (k!) \cdot g[n - k]\).
用线性方法递推阶乘、逆元、\(2\) 的整次幂以及 \(g\) 的取值可以做到 \(O(n)\) 复杂度。
代码
#include <cstdio>
using namespace std;
#define ll long long
const int maxn = 5e6 + 5;
const int lim = 5e6;
const int mod = 998244353;
int t, n, k;
int fac[maxn], invf[maxn], pw2[maxn], g[maxn];
void init()
{
fac[0] = fac[1] = invf[0] = invf[1] = pw2[0] = 1;
for (int i = 1; i <= lim; i++) pw2[i] = (pw2[i - 1] << 1) % mod;
for (int i = 1; i <= lim; i++) fac[i] = 1ll * fac[i - 1] * i % mod;
for (int i = 2; i <= lim; i++) invf[i] = 1ll * (mod - mod / i) * invf[mod % i] % mod;
for (int i = 1; i <= lim; i++) invf[i] = 1ll * invf[i - 1] * invf[i] % mod;
g[0] = 1, g[1] = 0;
for (int i = 2; i <= lim; i++) g[i] = 4ll * i * (i - 1) % mod * ((g[i - 1] + 2ll * (i - 1) * g[i - 2]) % mod) % mod;
}
ll C(int n, int m) { return (n < m ? 0 : 1ll * fac[n] * invf[m] % mod * invf[n - m]) % mod; }
ll query(int n, int k)
{
ll sq = C(n, k) * C(n, k) % mod;
return sq * fac[k] % mod * pw2[k] % mod * g[n - k] % mod;
}
int main()
{
init();
scanf("%d", &t);
while (t--)
{
scanf("%d%d", &n, &k);
printf("%lld\n", query(n, k));
}
return 0;
}