题意简述:有\(n\)个桶和\(2n-1\)个球,每个桶最多能装一个球,且第\(i\)个桶可以装前\(2i-1\)个球。问取\(m\)个桶和\(m\)个球,并将每个球放进一个桶里的方案数,\(q\)组询问。\(1\leq q\leq 10^5, 1\leq m\leq n\leq 10^7\)。
有一个显然的\(O(nm)\)dp:设\(f_{i, j}\)为前\(i\)个桶选了\(j\)个桶的方案数,考虑转移:
若第\(i\)个桶不选,则\(f_{i-1, j}\Rightarrow f_{i, j}\)。
若第\(i\)个桶选,则前\(2i-1\)个球中有\(j-1\)个已经被选,剩余的可以自由选择,即\(f_{i-1, j-1}\cdot (2i-j)\Rightarrow f_{i, j}\)。
然后我们就可以打表啦!
观察发现\(f_{n, m}=\binom{n}{m}^2\cdot m!\),但是这个结论需要证明。
由这个式子不难想到,\(n\times n\)的棋盘中放\(m\)个无标号的互不攻击的车也是这个方案数,证明可以设\(g_{i, j}\)为\(i\times i\)的棋盘放\(j\)个车的方案数,考虑棋盘第\(n\)行、列(记为\(S\))的情况:
\(S\)中无车:显然答案为\(g_{n-1, m}\)。
\(S\)中有一个车:考虑剩下的\((n-1)\times(n-1)\)的棋盘,显然方案数为\(g_{n-1, m-1}\),且剩余的能放车的位置有\(2n-2m+1\)个。
\(S\)中有两个车:考虑一个\(g_{n-1, m-1}\)的情况,对于其中任意一种方案,我们均可以从\(m-1\)个车中选出一个\((x, y)\),将它拆成\((x, n)\)和\((n, y)\)的两个车,显然这样的方案是一一对应的。
因此\(g_{i, j}=g_{i-1, j}+(2i-j) g_{i-1, j-1}\),发现与\(f\)的递推式完全一致。所以这个到底是怎么想到的啊
#include <cstdio> #include <cctype> #include <cstring> #include <cassert> #include <iostream> #include <algorithm> #define R register #define ll long long using namespace std; const int N = 10010000, T = 110000, mod = 998244353; int t, que[T][2], maxN; ll fac[N], inv[N]; template <class T> inline void read(T &x) { x = 0; char ch = getchar(), w = 0; while (!isdigit(ch)) w = (ch == '-'), ch = getchar(); while (isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar(); x = w ? -x : x; return; } inline ll quickpow(ll base, ll pw) { ll ret = 1; while (pw) { if (pw & 1) ret = ret * base % mod; base = base * base % mod, pw >>= 1; } return ret; } int main() { read(t); for (R int i = 1; i <= t; ++i) read(que[i][0]), read(que[i][1]), maxN = max(maxN, que[i][0]); fac[0] = 1; for (R int i = 1; i <= maxN; ++i) fac[i] = fac[i - 1] * i % mod; inv[maxN] = quickpow(fac[maxN], mod - 2); for (R int i = maxN - 1; ~i; --i) inv[i] = inv[i + 1] * (i + 1) % mod; for (R int i = 1; i <= t; ++i) { int x = que[i][0], y = que[i][1]; printf("%lld\n", fac[x] * fac[x] % mod * inv[y] % mod * inv[x - y] % mod * inv[x - y] % mod); } return 0; }