ABC134F 题解

· · 题解

传送门

建议先看其他题解,看不懂再来看我的QWQ,仅作补充或解释。

显然是一个 n^4 的 DP,转换成小球放进盒子里的这样一个二分图模型之后,可以设计状态 dp[i][j][k] 表示前 i 个盒子里有 j 个是空的且当前怪异度为 k 的情况下的总方案数。

考虑转移。对于编号为 i+1 的盒子和小球,有以下几种情况:

  1. 这两个互相配对。

  2. 当前盒子装之前没配对的小球,当前小球暂且保留。

  3. 当前盒子暂且保留,当前小球装入之前没配对的盒子。

  4. 当前盒子装之前没配对的小球,当前小球装入之前没配对的盒子。

  5. 当前盒子和小球都暂且保留。

然后就会出现当前最难以解决的一个问题:

只知道之前有几个盒子没配对,怎么知道是哪几个?如果不知道怎么转移 k?

显然,如果 k 只表示已配对的盒子和小球的怪异度,是无法实现接下来的转移的,我们还需要计算没有匹配的盒子小球对 k 的贡献。

但是,尚未匹配,怎么知道贡献?

我想提及一个大多数题解都一笔带过的方法:增量贡献

假设 1 号盒子和 1 号小球不匹配,那么 1 号盒子以后可能与其他号小球匹配,1 号小球同理。

在所有的匹配可能中,因为 1 号盒子和 1 号小球不匹配而对整体怪异度造成的最小贡献是 2,即 1 号盒子与 2 号小球匹配,2 号盒子与 1 号小球匹配的情况。

我们可以先把这个大小为 2 的最小贡献保存下来,即暂且认为最终匹配情况就是上文所说的那种情况,如果情况有变,就再更新贡献。

如果真是假设的情况,那么匹配时不需要再计算贡献,因为之前已经算好了。

什么时候“情况有变”?那当然是 2 号盒子不跟 1 号小球配对,或者 2 号小球不跟 1 号盒子配对啊。

如果 2 号盒子不跟 1 号小球配对而跟 2 号小球配对,我们就又暂且认为 1 号小球会跟 3 号小球配对,这时贡献只会增加 1,对 1 号盒子也同理。

如果 2 号盒子既不跟 1 号小球配对,也不跟 2 号小球配对,那我们就也暂且认为 1,2 号盒子都与 3 号小球配对,此时 2 号盒子产生 1 的贡献,1 号盒子的贡献加 1。

也就是说,对于 dp[i][j][k],我们可以把它的定义更新为:前 i 个盒子,其中有 j暂且认为与 i+1 号盒子/小球配对,其他都已配对,且当前怪异度为 k 的情况下的总方案数。

这样是不是好转移多了?来试试,对于上面的几种情况:

  1. 这一对盒子和小球不产生贡献,但是之前的所有假设作废,要重新暂且认为之前没配对的盒子/小球与编号为 i+2 的小球/盒子配对,贡献会在原来的基础上增加 2\times j

配个图说明,左边是盒子右边是小球,假设虚线是假设的边,实线是确定的边,红色虚线是作废的假设,当前考虑到第 3 组盒子小球。

如果 1 号、2 号小球和盒子都不匹配,是这样的:

他们都暂且认为与 3 号盒子/小球匹配,此时贡献为 6。

转移后,是这样的:

  1. 之前没配对的盒子的假设全部作废,除了选中的小球,其他之前没配对的小球的假设也作废,并且新增一个当前小球的假设。综合起来,贡献还是比之前多了 2\times j

  1. 由对称性可知,其实和 2 是一样的,图不配了。

  1. 所有盒子和小球的假设都作废,并且新增一对假设,贡献多 2\times(j+1)

代码很容易就写出来了,记得在转移的时候乘上可以选择的种类数。

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
constexpr ll P = 1000000007;
ll n, K, dp[55][55][55 * 55];
void add(ll& x, ll y) {
    x = (x + y) % P;
}
int main() {
    cin >> n >> K;
    dp[1][0][0] = dp[1][1][2] = 1;
    for (ll i = 1; i < n; i++) {
        for (ll j = 0; j <= i; j++) {
            for (ll k = 0; k <= n * n; k++) {
                add(dp[i + 1][j][k + 2 * j], dp[i][j][k]); // 情况 1
                if (j) {
                    add(dp[i + 1][j][k + 2 * j], j * dp[i][j][k] * 2); // 情况 2,3
                    add(dp[i + 1][j - 1][k + 2 * (j - 1)], j * j * dp[i][j][k]); // 情况 4
                }
                add(dp[i + 1][j + 1][k + 2 * (j + 1)], dp[i][j][k]); // 情况 5
            }
        }
    }
    cout << dp[n][0][K] << endl;
    return 0;
}