Loading

【题解】[JOISC2020] 最古の遺跡 3

做过最神的数数题之一。

先考虑如果给定 \(h\) ,如何求出 \(A\)

首先最后一个数一定会留下,倒数第二个数如果和最后一个数相等,则减一,否则一直保留下去。

所以模拟一下我们发现,从后向前扫一遍,如果后面没有出现 \(h_i\) ,则 \(h_i\) 就是当前位置的最终值,否则 \(h_i\) 一直减一直到后面没有出现 \(h_i\) ,减到 \(0\) 为止。

可以设计状态 \(f[i][S]\) 表示后 \(i\) 个数,出现过的数的集合是 \(S\) 的方案数,时间复杂度 \(\mathcal{O}(n^2 2^n)\) ,可以通过第一个子任务。

考虑优化,我们优化状态 \(f[i][j]\) 表示后 \(i\) 个数,出现过的数的集合是 \(1\sim j\) 的方案数,然后每次在 \(j\) 后面接上连续的一段转移转移到 \(f[i'][j+k]\)

接下来是愉快的分类讨论环节。

如果当前位置没有留下来,那么当前位置可选的数是 \(1\sim j\) ,因为选 \(>j\) 的数一定会使当前数留下来。我们记当前位置后面没有留下来的位置有 \(res\) 个,当前可以填的数有 \(2j\) 个,已经填了 \(j+res\) 个数,所以还剩 \(j-res\) 个数可以选择,\(f[i][j]=(j-res)\times f[i-1][j]\)

如果当前数留了下来。那么考虑是否在 \(j\) 后面接上连续的一段 \(k\)

如果不接上,那么就将当前位置留到后面计算,即贡献未来计算,\(f[i][j]=f[i-1][j]\)

否则我们枚举 \(k\) ,从 \(f[i-1][j-k]\) 转移到 \(f[i][j]\)

首先当前位置一定是的最终值 \(j-k+1\) ,因为如果 \(j-k+1\) 出现在其他位置,则贡献不在这里计算。

其中 \(j-k+2\sim j\)\(k-1\) 个数已经出现过,所以当前位置可填的数有这 \(k-1\) 个数,还有等于 \(j-k+1\) 的两个数,一共 \(k+1\) 个数。

然后钦定这 \(k-1\) 个数的位置,我们令后面留下的位置有 \(sum\) 个,固定前面的 \(j-k\) 个数用了 \(j-k\) 个位置,剩下 \(sum-j+k\) 个位置,方案数为 \(\dbinom{sum-j+k}{k-1}\)

最后固定这 \(k-1\) 个位置上的数的排列,我们记作 \(g_{k-1}\) 。即用 \(1\sim n\)\(2n\) 个数构成一个长度为 \(n\) 的序列使得最终序列为一个 \(1\sim n\) 的排列。

首先必要条件是:对于任意的 \(i\in [1,n]\) ,使用的 \(\le i\) 的数 \(\le i\) 。显然,如果 \(>i\) ,则至少存在一个数被减到 \(0\)

观察一下发现这同时是充分条件,因为这个序列一定优于一个长度为 \(n\) 的排列,而排列一定合法。

所以我们呢记录 \(g[i][j]\) 表示用 \(1\sim i\) 个数,填了 \(j\) 个位置,然后枚举第 \(i\) 个数填了 \(0/1/2\) 个数转移即可。

\[g[i][j]=g[i-1][j]+2j\times g[i-1][j-1]+j(j-1)\times g[i-1][j-2] \]

预处理组合数,时间复杂度 \(\mathcal{O}(N^3)\)

#include <bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define pre(i,a,b) for(int i=a;i>=b;i--)
#define N 605
#define P 1000000007
using namespace std;
int f[N << 1][N], g[N][N], n, u[N << 1], c[N][N];
int main() {
    scanf("%d", &n);
    rep(i, 1, n) {
        int x;
        scanf("%d", &x);
        u[x] = 1;
    }
    rep(i, 0, n) {
        c[i][0] = g[i][0] = 1;
        rep(j, 1, i)c[i][j] = (c[i - 1][j - 1] + c[i - 1][j]) % P;
        rep(j, 1, i) {
            g[i][j] = (g[i - 1][j] + 2LL * j * g[i - 1][j - 1]) % P;

            if (j > 1)
                g[i][j] = (g[i][j] + 1LL * g[i - 1][j - 2] * j * (j - 1)) % P;
        }
    }
    f[n * 2 + 1][0] = 1;
    int sum = 0;
    pre(i, n * 2, 1) {
        if (u[i]) {
            rep(j, 0, sum + 1) {
                f[i][j] = f[i + 1][j];
                rep(k, 1, j)
                f[i][j] = (f[i][j] + 1LL * f[i + 1][j - k] * c[sum - (j - k)][k - 1] % P * g[k - 1][k - 1] % P * (k + 1)) % P;
            }
            sum++;
        } else {
            int res = n * 2 - i - sum;
            rep(j, 0, sum)f[i][j] = 1LL * f[i + 1][j] * (j - res) % P;
        }
    }
    rep(i, 1, n)f[1][n] = 500000004LL * f[1][n] % P;
    printf("%d\n", f[1][n]);
    return 0;
}
posted @ 2021-06-25 09:12  7KByte  阅读(97)  评论(0编辑  收藏  举报