【题解】P4931 [MtOI2018] 情侣?给我烧了!(加强版)

发布时间 2023-07-12 02:29:15作者: kymru

不算堂堂的复活

原题链接 P4921 [MtOI2018] 情侣?给我烧了!

思路

推导 / 二项式反演 + 生成函数

这个题看到恰好 \(k\) 对其实很容易想到二项式反演,但是如果要推反演就需要很复杂的 GF 来简化,直接上简单的推导做法好了。

考虑分化一下问题,分成钦定恰好 \(k\) 对配对的情侣和 \(n - k\) 对错排的情侣。

钦定 \(k\) 对配对的情侣:

  1. 钦定配对的情侣,有 \({n \choose k}\) 种选法。

  2. 钦定 \(k\) 对座位,有 \({n \choose k}\) 种选法。

  3. 情侣之间可以左右交换位置,有 \(2^k\) 种情况。

  4. 每对情侣都可以任意选一对座位做,有 \(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;
}