SPOJ PERIODNI

SPOJ PERIODNI

仍然考虑类笛卡尔树的 dp,我们把整个直方图画成了很多个矩形。首先考虑一个矩形内部的答案,如果这个矩形 $n$ 行 $m$ 列,并且我们要从中选择 $k$ 个点,方案数量就是,从行选择 $k$ 个,从列选择 $k$ 个,然后再任意组合。考虑任意组合的过程,就是从行的 $k$ 个点分别对应到一个列,也就是排列的数量,所以就是 $k!$ ,于是 $n\times m$ 的矩形选择 $k$ 个点的方案就是:

然后考虑设 $dp[i][j]$ 表示 $i$ 这个(笛卡尔树的)节点中选择了 $j$ 个点的方案数量。

怎么转移?我们先抛开这个矩形本身,只考虑 $u$ 所有儿子,设初始状态为 $dp[u][0] = 1$,然后我们对于每一个儿子 $v$ ,考虑转移 (式子可能边界有点小问题建议以代码为准),注意树形 dp 要逆序枚举。。

对于所有儿子分别跑一次之后,当前的 $dp[u][i]$ 就是从 $u$ 的儿子中选择 $i$ 个点的方案数量,然后考虑怎么把这个位置的矩形也选择上,注意要减去已经通过儿子选择过的列,于是再做一次:

其中 $x,y$ 表示这个矩形的长宽。(这几个柿子看起来都好卷积啊。。但是 $n \leq 500$ 就随便做喽)

复杂度,如果笛卡尔树暴力枚举下一层暴力跑树形 dp 都才 $O(n^3)$ 。。那就暴力一点吧。。

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include "iostream"
#include "algorithm"
#include "cstring"
#include "cstdio"
#include "set"
#include "vector"
#include "map"
#include "cmath"
#define MAXN 506
//#define int long long
using namespace std;
typedef long long ll;
#define P 1000000007

int n , m;

int Pow( int x , int a ) {
int ans = 1 , cur = x % P;
while( a ) {
if( a & 1 ) ans = 1ll * ans * cur % P;
cur = 1ll * cur * cur % P , a >>= 1;
}
return ans;
}
int h[MAXN];
int J[1000006] , invJ[1000006];
int C( int a , int b ) {
if( a < b ) return 0;
return 1ll * J[a] * invJ[b] % P * invJ[a - b] % P;
}
int S( int n , int m , int k ) {
return 1ll * C( n , k ) * C( m , k ) % P * J[k] % P;
}
int dp[506 * 506][506] , cur , k;
int solve( int l , int r , int base ) {
if( l > r ) return 0;
int re = 0 , c = ++ cur , L = r - l + 1;
for( int i = l ; i <= r ; ++ i ) if( !re || h[i] < h[re] ) re = i;
vector<int> mn;
mn.push_back( l - 1 );
for( int i = l ; i <= r ; ++ i ) if( h[i] == h[re] ) mn.push_back( i );
mn.push_back( r + 1 );
dp[c][0] = 1;
for( int i = 1 ; i < mn.size() ; ++ i ) {
int t = mn[i] , p = mn[i - 1] , l = t - p - 1 , id = solve( p + 1 , t - 1 , h[re] );
if( id == 0 ) continue;
for( int j = min( L , k ) ; j >= 0 ; -- j )
for( int k = 1 ; k <= min( j , l ) ; ++ k )
( dp[c][j] += 1ll * dp[id][k] * dp[c][j - k] % P ) %= P;
}
int dh = h[re] - base;
for( int i = min( L , k ) ; i >= 0 ; -- i )
for( int j = 1 ; j <= min( dh , i ) ; ++ j )
( dp[c][i] += 1ll * dp[c][i - j] * S( dh , L - ( i - j ) , j ) % P ) %= P;
return c;
}

int main() {
J[0] = 1; invJ[0] = 1;
for( int i = 1 ; i < 1000006 ; ++ i ) J[i] = 1ll * J[i - 1] * i % P , invJ[i] = Pow( J[i] , P - 2 );
cin >> n >> k;
for( int i = 1 ; i <= n ; ++ i ) scanf("%d",h + i);
int c = solve( 1 , n , 0 );
cout << dp[c][k] << endl;
}
文章作者: yijan
文章链接: https://yijan.co/spoj-periodni/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Yijan's Blog