一个比较套路的题,难度是 *,只能说不算太难吧。读完这篇题解后,希望大家都能有所收获!


套路地,我们分层来考虑问题。我们将问题分为两个部分,即层内预处理、层与层之间的转移。

层内预处理:即我们需要计算用 种颜色填充长度为 的一层的方案数。设 表示使用了 种颜色,填充了长度为 的行,且相邻颜色不同的方案数(进考虑先对颜色模式,不考虑具体颜色值)。则我们有

假设我们选定了特定的 种颜色,则填充长度为 的一层的方案数为:。预处理的范围是 ,可以接受。

再说第二部分,即层与层之间的转移:设 表示考虑到第 层,且第 层已经恰好使用了 种颜色的方案数。首先,对于第 层,我们想选 种颜色,方案数是

由状态定义,我们有以下转移逻辑:总方案数 (前 层的所有方案) (第 层使用任意方案) 冲突方案。这里的冲突方案所要满足的条件是:

  • 层用的颜色数量也是
  • 颜色集完全相同,考虑列式子:
    • 首先是在 行使用特定的某组 个颜色的方案数:
    • 在第 层中使用上述方案的方案数是:
    • 对所有可能的情况求和,即冲突的总数为:

则我们有最终的 转移方程:,化简后就是:。直接把 优化掉,然后再把空间滚动一下即可。


时间复杂度:


代码:

#include <bits/stdc++.h>
#define int long long
using namespace std;

const int N = 5010, M = 1e6 + 10;
int g[N][N], P[N];
int l[M], fact[N];
int old[N], nw[N];

signed main() {
    ios::sync_with_stdio(false), cin.tie(0);
    int n, m, p;
    cin >> n >> m >> p;
    for (int i = 1; i <= n; i++) {
        cin >> l[i];
    }
    g[0][0] = 1, g[1][1] = 1;
    for (int i = 2; i < N; i++) {
        for (int j = 1; j <= i; j++) {
            g[i][j] = (g[i - 1][j - 1] + g[i - 1][j] * (j - 1)) % p;
        }
    }
    P[0] = fact[0] = 1;
    for (int i = 1; i < N; i++) {
        P[i] = P[i - 1] * (m - i + 1) % p;
        fact[i] = fact[i - 1] * i % p;
    }
    int sum = 0;
    for (int i = 1; i <= min(m, l[1]); i++) {
        old[i] = P[i] * g[l[1]][i] % p;
        sum = (sum + old[i]) % p;
    }
    for (int i = 2; i <= n; i++) {
        int nw_sum = 0;
        for (int j = 1; j <= min(m, l[i]); j++) {
            int val1 = P[j] % p * sum % p;
            int val2 = 0;
            if (j <= min(m, l[i - 1])) {
                val2 = fact[j] % p * old[j] % p;
            }
            nw[j] = g[l[i]][j] * (val1 - val2 + p) % p;
            nw_sum = (nw_sum + nw[j]) % p;
        }
        sum = nw_sum;
        memcpy(old, nw, sizeof(nw));
    }
    cout << (sum + p) % p << "\n";
    return 0;
}