LOJ 2743 JOI Open 2016 摩天大楼

神题!

LOJ 2743 JOI Open 2016 摩天大楼

神仙题!

我们考虑给数字排序一个一个加入排列。

考虑当前整个长度为 $n$ 的数列被加入了一些数字,形成了一些连续段。当我们即将加入 $A_i$ 时,我们钦定每个连续段的左右两端的仍然没有放下确定的差值为 $A_i-A_{L/R}$ ,然后当前放了一个数字在一个地方,如果它作为了某个段的左端 / 右端,那么这个差值就从 不确定的 变成了 确定的,并且除开这个左端(但是包含新加入的左端)的不确定的差值都会加上 $A_{i+1} - A_i$。或者还有种理解方式,我们可以看成当前有一个直线 $y=A_i$ 并且考虑所有加入过的数字和这个直线之间的差值,具体来说就像是这种情况:

其中直线上面本应该还有一些即将被加入的点,但是暂时贡献设置为 $A_i - A_{L/R}$

除此之外,我们还要考虑一些其他情况,比如合并两个段,新加一个段,我们设 dp 状态为 $dp[i][j][k][h]$ 表示当前考虑第 $i$ 个数字,一共有 $j$ 个段,当前总的权值和(包括确定的和不确定的)为 $k$ ,$h = 0/1/2$ 表示边界(1位置和 n 位置)被占用的数量,此时的方案数,显然最后的答案是

我们考虑 $k$ 的转移,假设当前状态为 $dp[i][j][k][h]$ ,无论在哪里加入一个点,$k$ 都会增加 $(A_{i+1}-A_i)\times(2j - h)$ ,我们设这个值是 $w$

总结一下,大概有如下情况:

  • 新建一个段,把它放在一个空隙,并且钦定它不是边界,注意,这里以及后面的空隙并不是指真正的某一个位置,而只是把它插入到这个相对的位置,当然,如果有一个边界已经有东西了我们不能往这个边界段的靠向边界的方向插入。

  • 新建一个段,钦定它是边界:

  • 合并两个段,考虑一共有 $j-1$ 个位置可以把两个段合并掉,这 $j-1$ 歌位置显然不会受边界影响,这种状态必须满足 $j\ge 2$:

  • 放在一个段的一边,并且钦定不是边界,这种状态显然必须满足 $j > 0$:

  • 放在一个段的一边,钦定它是边界,这种状态显然必须满足 $j > 0$:

复杂度是 $O(n^2L)$ ,状态 $O(n^2L)$ 转移 $O(1)$

然后这么写一发交上去发现只能过 subtask 2

这题有坑,注意如果 $n=1$ 显然你不可能放满两个边界啊。。所以直接判掉。

然后还得考虑,第一个数字是没有贡献的,所以应当初始化的时候 $A[0] = A[1]$ 。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include "iostream"
#include "algorithm"
#include "cstring"
#include "cstdio"
#include "set"
#include "vector"
#include "map"
#define MAXN 106
//#define int long long
using namespace std;
typedef long long ll;
const double eps = 1e-7;
#define P 1000000007
int n , L;
int A[MAXN];
int dp[2][MAXN][1006][4];
int main() {
cin >> n >> L;
if( n == 1 ) return puts("1") , 0;
for( int i = 1 ; i <= n ; ++ i ) scanf("%d",A + i);
sort( A + 1 , A + 1 + n );
int cur = 0 , las = 1;
dp[0][0][0][0] = 1 , A[0] = A[1];
for( int i = 0 ; i < n ; ++ i ) {
cur ^= 1 , las ^= 1;
memset( dp[cur] , 0 , sizeof dp[cur] );
for( int j = 0 ; j <= i ; ++ j ) {
for( int k = 0 ; k <= L ; ++ k ) {
for( int h = 0 ; h < 3 ; ++ h ) {
if( !dp[las][j][k][h] ) continue;
int w = k + ( A[i + 1] - A[i] ) * ( 2 * j - h );
if( w < 0 || w > L ) continue;
int c = dp[las][j][k][h];
( dp[cur][j + 1][w][h] += 1ll * c * ( j + 1 - h ) % P ) %= P;
( dp[cur][j + 1][w][h + 1] += 1ll * c * ( 2 - h ) % P ) %= P;
if( j ) ( dp[cur][j - 1][w][h] += 1ll * c * ( j - 1 ) % P ) %= P;
if( j ) ( dp[cur][j][w][h] += 1ll * c * ( 2 * j - h ) % P ) %= P;
if( j ) ( dp[cur][j][w][h + 1] += 1ll * c * ( 2 - h ) % P ) %= P;
}
}
}
}
int res = 0;
for( int i = 0 ; i <= L ; ++ i ) ( res += dp[cur][1][i][2] ) %= P;
cout << res << endl;
}
文章作者: yijan
文章链接: https://yijan.co/loj-2743-joi-open-2016-mo-tian-da-lou/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Yijan's Blog