分析
题目相当于限定只能放在两个圆中间,求放 $2n$ 个车的方案数。
事实上我们可以对每一列求出其能放置车的范围 $[L_i, R_i]$。可以发现,对于所有 $i\in[n, 2n)$,都有 $L_i = 0$。
如果所有 $L_i$ 都为 $0$,这就是一个经典问题:将 $R_i$ 从小到大排序,答案即为 $\prod_{i = 0}^{2n - 1} (R_i + 1 - i)$。
然而现在有 $n$ 个元素的 $L_i$ 不为 $0$。考虑容斥,则我们需要对每个 $k$ 求出有恰好 $k$ 个车放在了小圆 $x^2 + y^2 = n^2$ 内的方案数。
这种问题可以考虑 DP。
我们首先确定一个 DP 的顺序:对于 $[0, n)$ 中的点,设其权值为 $L_i - 1$;对于 $[n, 2n)$ 中的点,设其权值为 $R_i$;然后把所有点按照权值从小到大的顺序排序。另外可能有权值相同的情况,这时候我们规定靠右的点排在前面。这样子排序的好处下面就会看到。
现在考虑如何设状态。设 $dp_{i, j}$ 表示前 $i$ 个点、有 $j$ 个在小圆内的方案数。转移讨论一下:
-
如果当前点在 $[n, 2n)$ 中,比它小(“$a$ 比 $b$ 小”定义为按照这种方式排序后 $a$ 排在 $b$ 的前面)的有:排在其前面的所有 $[n, 2n)$ 中的元素、排在其前面的 $[0, n)$ 中的在小圆内的元素。第一类可以直接统计出来,设为 $sr$;第二类总共有 $j$ 个。于是有转移
$$
dp_{i + 1, j} \gets_+ dp_{i, j} \times (R + 1 - (sr + j))
$$ -
如果当前点在 $[0, n)$ 中,再进行讨论:
-
如果当前点在小圆内,比它小的与上面的情况类似,于是有转移
$$dp_{i + 1, j + 1} \gets_+ dp_{i, j} \times (L - (sr + j))$$ -
如果当前点不在小圆内,比它小的有:所有小圆内的元素、所有 $[n, 2n)$ 中的元素、排在其前面的所有 $[0, n)$ 中的不在小圆内的元素。设排在其前面的 $[0, n)$ 中的元素有 $sl$ 个,则第一类有 $k$ 个、第二类有 $n$ 个、第三类有 $sl - j$ 个。于是有转移
$$dp_{i + 1, j} \gets_+ dp_{i, j} \times (R + 1 - (k + n + sl - j))$$
-
$dp_{2n, k}$ 即为所求的方案数。
一次 DP 是 $\mathcal{O}(n ^ 2)$ 的,所以总时间复杂度 $\mathcal{O}(n ^ 3)$。
代码
// ====================================
// author: M_sea
// website: https://m-sea-blog.com/
// ====================================
#include <bits/stdc++.h>
#define file(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)
#define debug(...) fprintf(stderr, __VA_ARGS__)
using namespace std;
typedef long long ll;
int read() {
int X = 0, w = 1; char c = getchar();
while (c < '0' || c > '9') { if (c == '-') w = -1; c = getchar(); }
while (c >= '0' && c <= '9') X = X * 10 + c - '0', c = getchar();
return X * w;
}
const int N = 500 + 10;
int n, mod;
int L[N], R[N], p[N];
int dp[N][N];
int calc(int k) {
memset(dp, 0, sizeof(dp)); dp[0][0] = 1;
for (int i = 0, sl = 0, sr = 0; i < n << 1; ++i) {
int x = p[i];
if (x < n) {
for (int j = 0; j <= sl; ++j) {
dp[i + 1][j + 1] = (dp[i + 1][j + 1] + 1ll * dp[i][j] * (L[x] - (sr + j))) % mod;
dp[i + 1][j] = (dp[i + 1][j] + 1ll * dp[i][j] * (R[x] + 1 - (n + k + sl - j))) % mod;
}
++sl;
} else {
for (int j = 0; j <= sl; ++j)
dp[i + 1][j] = (dp[i + 1][j] + 1ll * dp[i][j] * (R[x] + 1 - (sr + j))) % mod;
++sr;
}
}
return dp[n << 1][k];
}
int main() {
n = read(), mod = read();
for (int i = 0; i < n; ++i) L[i] = ceil(sqrt(n * n - i * i));
for (int i = 0; i < n << 1; ++i) R[i] = min(2.0 * n - 1, floor(sqrt(4 * n * n - i * i)));
for (int i = 0; i < n << 1; ++i) p[i] = i;
sort(p, p + n + n, [](int x, int y) {
int wx = x < n ? L[x] - 1 : R[x], wy = y < n ? L[y] - 1 : R[y];
return wx < wy || (wx == wy && x > y);
});
int ans = 0;
for (int i = 0; i <= n; ++i) {
if (i & 1) ans = (ans - calc(i) + mod) % mod;
else ans = (ans + calc(i)) % mod;
}
printf("%d\n", ans);
return 0;
}