AT ARC171E Rookhopper's Tour
首先会发现路径肯定是经过一个石子后之后的方向会与此时方向垂直。
这是因为如果继续沿这个方向就说明这一行 / 列一定存在至少两个石子,不满足条件。
于是 \(m\) 为奇数肯定不满足条件,其次 \(m = 2\) 也肯定不满足条件。
所以不妨把拐角的位置连起来成一个路径,此时其实需要证明路径和石子会构成一个双射,也即是证明不存在一个石子的分配方式使得有多于一种路径,但是我发现我好像并不会证明。
于是就可以结合上对应的路径来考虑计数了。
但是直接给出沿路经石子的位置依然不好判定是否合法。
不过考虑到其实在路径的过程中,因为是横竖交替的,所以必有一维的坐标是知道的。
于是只知道这个路径的起始方向,以及沿路经石子的其中一维坐标,就可以判定这个路径是否合法。
然后就可以考虑计数了,考虑以下性质:
-
横向的石子与竖向的石子是独立的,只需要保证中间是横竖交替的即可。
这是因为例如横石子,会发现下一步的纵坐标在何处其实只决定于上一步的纵坐标,只能由横石子来改变。 -
每次经过一个横石子会让 \(2\) 个列变得不合法,竖石子同理。(这里就不会了。)
这是因为首先这一列不能选了,其次接下来走到的那一列也必然有石子也不能选。 -
对于一个方向,最后一个石子的坐标是根据最后回来的方向固定的。
-
对于一个方向,最后两个石子对于终点(也是起点)必须在同一边。
这是为了保证最后落到的横竖坐标为终点。
接下来考虑利用性质计数:
- 性质 1 说明了,可以对横竖分别计数,最后相乘再 \(\times 2\) 即可(起始方向)。
- 性质 2 说明了,如果对于一边共有 \(n\) 行,需要有 \(k\) 个石子需要放,那么方案数就是 \(\binom{n - k}{k}\times k!\)。
\(\binom{n}{k}\) 相当于是把每个石子限制的两行捆绑在一起考虑,\(k!\) 则是为了决定经过的顺序,会发现在顺序确定的时候对于被捆绑的两行是容易分清石子在哪行的。 - 性质 \(3, 4\) 说明了,枚举最后回来的方向,记这一边的空行有 \(x\) 个,另一边的空行有 \(y\) 个(都不含起点所在的行)。
那么方案数可以表示为 \(\sum\limits_{i = 1}^{\frac{m}{2} - 1} \binom{x - 1 - i}{i}\binom{y - (\frac{m}{2} - 1 - i)}{\frac{m}{2} - 1 - i}i(m - 2)!\),\(x - 1\) 是因为实际上有一行已经被最后一个石子限制了,\(i\) 是倒数第二颗石子的方案数,\((m - 2)!\) 是其余石子的方案数。
时间复杂度 \(\mathcal{O}(n)\)。
#include <bits/stdc++.h>
#include <atcoder/all>
using mint = atcoder::modint998244353;
constexpr int maxn = 2e5 + 10;
int n, m;
mint fac[maxn], ifac[maxn];
inline mint binom(int n, int m) {
return n < m ? 0 : (fac[n] * ifac[n - m] * ifac[m]);
}
inline mint calc(int d) {
const int x = d - 1, y = n - d - 1;
mint ans = 0;
for (int i = 1; i < m; i++) {
ans += binom(x - i, i) * binom(y - (m - 1 - i), m - 1 - i) * i * fac[m - 2];
}
return ans;
}
int main() {
scanf("%d%d", &n, &m);
if (m <= 2 || m % 2) return puts("0"), 0;
m /= 2;
fac[0] = 1;
for (int i = 1; i <= n; i++) fac[i] = fac[i - 1] * i;
ifac[n] = fac[n].inv();
for (int i = n; i >= 1; i--) ifac[i - 1] = ifac[i] * i;
int a, b;
scanf("%d%d", &a, &b);
printf("%d\n", ((calc(a - 1) + calc(n - a)) * (calc(b - 1) + calc(n - b)) * 2).val());
return 0;
}
浙公网安备 33010602011771号