「模擬賽20211006」Disastrous Rain
題目
門前有一道很深的溝,呈一排方格狀。其餘部分都平平整整的,唯獨有連續的 \(n\) 格坑坑窪窪。這些坑窪的格子被從 1 開始編號,從溝底開始算,第 \(i\) 格的高度為一個正整數 \(h_i\)。
天下大雨,於是坑窪的部分會產生積水,而平整的部分的水會被直接排掉。考慮某個豎直切面,如果某個空白的點左側和右側均有格子高度不低於它,那麼這個點就會積水。顯然,如果格子高度都是整數,那麼積水體積也會是整數。
坑窪的地面不好看,你決定平整某些格子——即,選出一些格子使其高度變為 0。但是,你發現這可是個體力活,你只能平整恰好 \(k\) 個格子。現在你想知道,在所有的 \(\binom{n}{k}\)
資料範圍:對於 \(100\%\) 的資料,滿足 \(1\le n\le 25000,1\le h_i\le 10^9,1\le k\le \min\{25,n-1\}\)。
分析
對於最終的高度局面,我們可以得到積水總體積為:
\[\sum_{k=1}^n\min\{\max_{1\le j\le k}h_j,\max_{k\le j\le n}h_k\}-h_k \]注意到計算過程只與字首、字尾最大值有關,因此可以設計一種 DP:
設 \(f_{i,k,p,s,0/1}\) 表示考慮了前 \(i\) 個格子,當前有 \(k\) 個被平整,此時字首最大高度為 \(p\)
重要的觀察:由於最多平整 \(k\) 個格子,所以說,\(p,s\) 分別最多隻有 \(k+1\) 種取值。所以這個 DP 是 \(O(nk^3)\) 的。
這個複雜度雖然已經很不錯了,但是我們還需要繼續優化。
注意到,最終高度局面中,最高的格子一定不會貢獻任何積水,但是最高的格子一定可以當作邊界來用:
例如,中間的黃色格子就是最高的;當我們考慮字首紅格子的時候,我們可以假定後面存在一個更高的格子;同樣的,考慮字尾綠格子的時候,我們也可以假定前面存在一個更高的格子。
因此可以重新設計狀態:設 \(g_{i,j,k,0/1}\)
列舉最大值需要考慮多個最大值的情況——因此我們總是在列舉第一個最大值。
這個做法還可以優化!
對於所有方案,我們可以將它們劃分為兩類——積水體積為偶數的和積水體積為奇數的。注意到,我們已經知道了兩類方案數的和,我們只需要再知道它們的差即可。因此我們可以拋棄 DP 的最後一維,而直接維護 偶數 - 奇數 即可。
小結:
- 注意關鍵性質,例如本題中取值可能性的性質;
- 不一定需要從頭 DP 到尾,如果問題有一些特殊的資訊可以列舉,那麼我們可以在 DP 外進行列舉;
- 充分利用已知資訊;
程式碼
#include <set>
#include <cstdio>
#include <vector>
#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 = 1e9 + 7;
const int MAXN = 25005, MAXK = 30;
template<typename _T>
void read( _T &x )/*{{{*/
{
x = 0; char s = getchar(); bool f = false;
while( ! ( '0' <= s && s <= '9' ) ) { f = s == '-', s = getchar(); }
while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
if( f ) x = -x;
}/*}}}*/
template<typename _T>
void write( _T x )/*{{{*/
{
if( x < 0 ) putchar( '-' ), x = -x;
if( 9 < x ) write( x / 10 );
putchar( x % 10 + '0' );
}/*}}}*/
template<typename _T>
_T MAX( const _T a, const _T b )/*{{{*/
{
return a > b ? a : b;
}/*}}}*/
int f[2][MAXK][MAXK];
int g[MAXN][MAXK][MAXK];
int suF[MAXK], suG[MAXK];
std :: set<int> s;
std :: vector<int> pref[MAXN], suff[MAXN];
int h[MAXN], stk[MAXN];
int N, K;
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 Add( int x, int v ) { return ( x += v ) >= mod ? x - mod : x; }
inline void Upt( int &x, const int v ) { x = Add( x, v ); }
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 main()
{
read( N ), read( K );
rep( i, 1, N ) read( h[i] );
rep( i, 0, N )
{
s.insert( h[i] );
if( s.size() > ( unsigned ) K + 1 ) s.erase( s.begin() );
for( auto x : s ) pref[i].push_back( x );
}
s.clear();
per( i, N + 1, 1 )
{
s.insert( h[i] );
if( s.size() > ( unsigned ) K + 1 ) s.erase( s.begin() );
for( auto x : s ) suff[i].push_back( x );
}
g[N + 1][0][0] = 1;
per( i, N + 1, 2 ) // transition for g{{{
{
int p1 = 0, p2 = 0;
int n = suff[i].size(), m = suff[i - 1].size();
for( int j = 0 ; j < n ; j ++ )
for( int k = 0 ; k <= K ; k ++ )
{
if( ! g[i][j][k] ) continue;
int curH = MAX( suff[i][j], h[i - 1] );
for( ; p1 < m && suff[i - 1][p1] < curH ; p1 ++ );
bool coe = ( curH & 1 ) ^ ( h[i - 1] & 1 );
Upt( g[i - 1][p1][k], coe ? mod - g[i][j][k] : g[i][j][k] );
if( k == K ) continue; curH = suff[i][j];
for( ; p2 < m && suff[i - 1][p2] < curH ; p2 ++ );
coe = curH & 1, Upt( g[i - 1][p2][k + 1], coe ? mod - g[i][j][k] : g[i][j][k] );
}
}/*}}}*/
int ans = 0;
int pre = 1, nxt = 0;
f[nxt][0][0] = 1;
rep( i, 0, N - 1 )
{
// Calculation for answer
rep( k, 0, K ) suF[k] = suG[k] = 0;
int n = pref[i].size(), m = suff[i + 2].size();
for( int j = 0 ; j < n && pref[i][j] < h[i + 1] ; j ++ )
rep( k, 0, K ) Upt( suF[k], f[nxt][j][k] );
for( int j = 0 ; j < m && suff[i + 2][j] <= h[i + 1] ; j ++ )
rep( k, 0, K ) Upt( suG[k], g[i + 2][j][k] );
for( int j = 0 ; j <= K ; j ++ )
Upt( ans, Mul( suF[j], suG[K - j] ) );
// transition
int p1 = 0, p2 = 0; pre ^= 1, nxt ^= 1;
n = pref[i].size(), m = pref[i + 1].size();
for( int j = 0 ; j < m ; j ++ )
for( int k = 0 ; k <= K ; k ++ )
f[nxt][j][k] = 0;
for( int j = 0 ; j < n ; j ++ )
for( int k = 0 ; k <= K ; k ++ )
{
if( ! f[pre][j][k] ) continue;
int curH = MAX( pref[i][j], h[i + 1] );
for( ; p1 < m && pref[i + 1][p1] < curH ; p1 ++ );
bool coe = ( curH & 1 ) ^ ( h[i + 1] & 1 );
Upt( f[nxt][p1][k], coe ? mod - f[pre][j][k] : f[pre][j][k] );
if( k == K ) continue; curH = pref[i][j];
for( ; p2 < m && pref[i + 1][p2] < curH ; p2 ++ );
coe = curH & 1, Upt( f[nxt][p2][k + 1], coe ? mod - f[pre][j][k] : f[pre][j][k] );
}
}
int all = 1;
rep( i, 1, K ) all = Mul( all, Mul( Inv( i ), N - i + 1 ) );
write( Mul( Inv( 2 ), Add( all, ans ) ) ), putchar( '\n' );
return 0;
}