[NOI2017]泳池
题目
点这里看题目。
分析
首先不难通过容斥将问题转化为任何一个矩形都不超过 \(K\) 这样的问题。
同时注意到最终影响最大矩形的只有每一列上从底部开始连续的一段安全水域的长度,我们将第 \(i\) 列的称为 \(h_i\),则合法的条件是:
\[\forall 1\le l\le r\le n, \min_{l\le k\le r}\{h_k\}\times (r-l+1)\le K
\]
这个结构在提醒我们实际上只有区间最小值在起作用,因此可以考虑对序列的笛卡尔树进行 DP。
由于只会跟序列长度相关,因此设 \(dp_{i,j}\) 表示长度为 \(i\) 的序列中,第一行到第 \(j\) 行都安全,第 \(j+1\) 行必有不安全的水域且满足条件的概率。
可以枚举 \(h\) 的最小值的位置进行转移:
\[dp_{i,j}=q^j(1-q)\sum_{k=1}^{i}\left(\sum_{l>j}dp_{k-1,l}\right)\left(\sum_{l\ge j}dp_{i-k,l}\right)
\]
使用前缀和优化之后,转移可以做到 \(O(n)\),而总的状态数可以算出为 \(n+\sum_{j=1}^{K}\lfloor\frac K j\rfloor\),在 \(n,K\) 同阶时可以看作 \(O(K\ln K)\)。此时这个 DP 就是 \(O(nK\ln K)\)。这样可以获得 70 pts。
注意到,DP 的状态数在 \(j>0\) 的时候都可以接受,唯独当 \(j=0\) 的时候状态数暴涨,因此我们单独拿出来做一遍。
因此设 \(f_i=dp_{i,0}\),它的转移是:
\[f_i=
\begin{cases}
\sum_{k>0}dp_{i,k}+(1-q)\sum_{j=1}^if_{i-j}\sum_{k>0}dp_{j-1,k}&i\le K\\
(1-q)\sum_{j=1}^Kf_{i-j}\sum_{k>0}dp_{j-1,k}&i>K
\end{cases}
\]
可以发现,当 \(i>K\) 的时候,\(f_i\) 的转移其实就是常系数齐次线性递推。因此我们可以很快地算出其第 \(n\) 项的值。
不会吧,这都 1202 年了,不会还有人在用多项式取模算常系数齐次线性递推吧
这里因为 \(K\) 比较小,因此可以直接暴力做多项式运算。
小结:
- 这里的优化比较好。把状态数较大但有有更简洁形式的 \(dp_{i,0}\) 单独拿出来算,从而降低了复杂度。我们可以通过观察状态数或转移方式来划分 DP。
- 写 \(dp\) 的时候一定要理清思路,避免调半天调不出来
代码
#include <cstdio>
#include <cstring>
#define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
#define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )
const int mod = 998244353;
const int MAXN = 2005;
template<typename _T>
void read( _T &x )
{
x = 0; char s = getchar(); int f = 1;
while( s < '0' || '9' < s ) { f = 1; if( s == '-' ) f = -1; s = getchar(); }
while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
x *= f;
}
template<typename _T>
void write( _T x )
{
if( x < 0 ) putchar( '-' ), x = -x;
if( 9 < x ) write( x / 10 );
putchar( x % 10 + '0' );
}
int pw[MAXN];
int f[MAXN], A[MAXN], B[MAXN], V[MAXN], U[MAXN], T[MAXN];
int dp[MAXN][MAXN], s[MAXN][MAXN];
int N, Q, R, LIM;
inline int Qkpow( int, int );
inline int Mul( int x, int v ) { return 1ll * x * v % mod; }
inline int Inv( const int a ) { return Qkpow( a, mod - 2 ); }
inline int Sub( int x, int v ) { return ( x -= v ) < 0 ? x + mod : x; }
inline int Add( int x, int v ) { return ( x += v ) >= mod ? x - mod : x; }
inline int Qkpow( int base, int indx )
{
int ret = 1;
while( indx )
{
if( indx & 1 ) ret = Mul( ret, base );
base = Mul( base, base ), indx >>= 1;
}
return ret;
}
int Calc( const int K )
{
//A: K - 1, B: K, V: 2K, U: 2K-1
for( int n = N ; n ; n >>= 1 )
{
memset( V, 0, sizeof V );
memset( U, 0, sizeof U );
for( int i = 0 ; i <= K ; i ++ )
T[i] = i & 1 ? mod - B[i] : B[i];
for( int i = 0 ; i <= K ; i ++ )
for( int j = 0 ; j <= K ; j ++ )
V[i + j] = Add( V[i + j], Mul( B[i], T[j] ) );
for( int i = 0 ; i < K ; i ++ )
for( int j = 0 ; j <= K ; j ++ )
U[i + j] = Add( U[i + j], Mul( A[i], T[j] ) );
for( int i = 0 ; i <= K ; i ++ ) B[i] = V[i << 1];
for( int i = 0 ; i < K ; i ++ ) A[i] = U[( i << 1 ) | ( n & 1 )];
}
return Mul( A[0], Inv( B[0] ) );
}
int Ask( const int K )
{
for( int i = 0 ; i <= K ; i ++ )
for( int j = 0 ; j < MAXN ; j ++ )
s[i][j] = dp[i][j] = 0;
for( int j = 1 ; j < MAXN ; j ++ ) dp[0][j] = s[0][j] = 1;
for( int i = 1 ; i <= K ; i ++ )
for( int j = K / i ; j ; j -- )
{
for( int k = 1 ; k <= i ; k ++ )
dp[i][j] = Add( dp[i][j], Mul( s[k - 1][j + 1], s[i - k][j] ) );
dp[i][j] = Mul( dp[i][j], Mul( pw[j], R ) );
s[i][j] = Add( s[i][j + 1], dp[i][j] );
}
f[0] = 1;
for( int i = 1 ; i <= K ; i ++ )
{
f[i] = s[i][1];
for( int j = 1 ; j <= i ; j ++ )
f[i] = Add( f[i], Mul( f[i - j], Mul( s[j - 1][1], R ) ) );
}
if( N <= K ) return f[N];
//F(x) = F(x) * A(x) + P(x)
//F: K, A: K + 1
memset( B, 0, sizeof B );
memset( A, 0, sizeof A );
for( int i = 1 ; i <= K + 1 ; i ++ )
B[i] = mod - Mul( s[i - 1][1], R );
B[0] = 1;
for( int i = 0 ; i <= K + 1 ; i ++ )
for( int j = 0 ; j <= K ; j ++ )
if( i + j <= K ) A[i + j] = Add( A[i + j], Mul( B[i], f[j] ) );
return Calc( K + 1 );
}
int main()
{
// freopen( "pool.in", "r", stdin );
// freopen( "pool.out", "w", stdout );
int x, y, K;
read( N ), read( K ), read( x ), read( y );
Q = Mul( x, Inv( y ) ), R = Sub( 1, Q );
pw[0] = 1;
for( int i = 1 ; i < MAXN ; i ++ )
pw[i] = Mul( pw[i - 1], Q );
int a = Ask( K - 1 ),
b = Ask( K );
write( Sub( b, a ) ), putchar( '\n' );
return 0;
}