题解:P7213 [JOISC2020] 最古の遺跡 3

_Cheems

2024-11-20 15:56:58

Solution

厉害数数题,使我螺旋升天。然后是小蒟蒻看完题解后的感想,不得不说第一眼看是真的一头雾水。

先考虑给定初始高度如何求最终高度。题目说每次地震会使得相同高度的两个柱子(显然至多两个)中靠前的那个高度减一,不可能模拟整个过程,所以需要观察一些性质:

记一段后缀的高度集合为 A,那么经过若干轮地震后高度集合 B 必然满足 A\in B,即地震后高度集合元素不减。这不难归纳证明。

这启示我们从后往前逐个确定最终高度,已确定 [i+1,2n],现考虑 i。由性质知只需关注 [i+1,2n] 中柱子的最终高度。具体而言,记 h_i 为当前柱子的初始高度,H[i+1,2n] 中柱子的高度集合,那么若 H 中存在极大连续段 [k,h_i] 则当前柱子高度最终变成 k-1

考虑 dp,记 f_{i,j} 为后 i 个柱子高度集合中存在极大连续段 [1,j] 的方案数。为了方便计数,认为同一高度的柱子不一样(两种颜色),最终除 2^n 即可。

考虑转移,记 c_0,c_1 为此前消失、保留的柱子数量:

最后考虑 g_k 怎么求,不妨设构成的连续段为 [1,k]。同 f 的转移,枚举最后添加的柱子高度 i,那么此前连续段 [1,i-1][i+1,k] 的形成完全独立,这不难理解。

于是有转移:g_k=\sum\limits_{i=1}^k {{k-1}\choose {i-1}}\times g_{i-1}\times g_{k-i}\times (k-i+2)

复杂度 O(n^3),跑的挺快。

代码

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

#define int long long
#define ADD(a, b) a = (a + b) % mod
const int N = 1.2e3 + 5, mod = 1e9 + 7, inv2 = 5e8 + 4;
int n, a, g[N], f[N][N], jc[N], jcinv[N];
bool vis[N];

inline int qstp(int a, int k) {int res = 1; for(; k; a = a * a % mod, k >>= 1) if(k & 1) res = res * a % mod; return res;}
inline int C(int n, int m){
    if(n < 0 || m < 0 || n < m) return 0;
    return jc[n] * jcinv[m] % mod * jcinv[n - m] % mod;
}
signed main(){
    jc[0] = jcinv[0] = 1;
    for(int i = 1; i < N; ++i) jcinv[i] = qstp(jc[i] = jc[i - 1] * i % mod, mod - 2);

    cin >> n;
    for(int i = 1; i <= n; ++i) scanf("%lld", &a), vis[a] = true;

    g[0] = f[0][0] = 1;
    for(int i = 1; i <= n; ++i)
        for(int j = 1; j <= i; ++j)
            ADD(g[i], g[j - 1] * g[i - j] % mod * (i - j + 2) % mod * C(i - 1, j - 1) % mod);

    int c0, c1 = 0;
    for(int i = 1; i <= 2 * n; ++i){
        c0 = i - 1 - c1;
        if(!vis[2 * n - i + 1]){
            for(int j = 0; j <= n; ++j) 
                if(j > c0) ADD(f[i][j], f[i - 1][j] * (j - c0) % mod);
        }
        else{
            for(int j = 0; j <= n; ++j){
                if(n > j + 1) ADD(f[i][j], f[i - 1][j]); 
                for(int k = j + 1; k <= n; ++k)
                    ADD(f[i][k], f[i - 1][j] * g[k - j - 1] % mod * C(c1 - j, k - j - 1) % mod * (k - j + 1) % mod);
            }
        }
        c1 += vis[2 * n - i + 1];
    }

    cout << f[2 * n][n] * qstp(inv2, n) % mod;
    return 0;
}