题解:P7213 [JOISC2020] 最古の遺跡 3
厉害数数题,使我螺旋升天。然后是小蒟蒻看完题解后的感想,不得不说第一眼看是真的一头雾水。
先考虑给定初始高度如何求最终高度。题目说每次地震会使得相同高度的两个柱子(显然至多两个)中靠前的那个高度减一,不可能模拟整个过程,所以需要观察一些性质:
记一段后缀的高度集合为
这启示我们从后往前逐个确定最终高度,已确定
考虑 dp,记
考虑转移,记
-
当前柱子应消失:对
j 无影响,只需考虑当前柱子的起始高度。由结论可知添加柱子的过程中连续段一定扩大,所以此前消失的柱子高度必然\le j 。那么有2j-j-c_0=j-c_0 种选择。即f_{i-1,j}\times (j-c_0)\to f_{i,j} 。 -
当前柱子应保留:需要根据对连续段
[1,j] 的影响分类:-
最终高度
>j+1 :无影响,但是可能影响其它连续段,怎么办?可以暂时存着,记为“待定柱”,等到连续段合并时再考虑其取值。即f_{i-1,j}\to f_{i,j} 。 -
最终高度
=j+1 :会合并连续段,枚举[1,j] 通过j+1 与[j+1,k] 合并。共有c_1-j 个待定柱,抽出一些构成连续段[j+1,k] ,不妨记g_k 为k 个柱子最终构成大小为k 的连续段的方案数。转移:f_{i-1,j}\times {{c_1-j}\choose {k-j-1}}\times g_{k-j-1}\times (k-j+1)\to f_{i,k} 。
-
最后考虑
于是有转移:
复杂度
代码
#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;
}