1. 程式人生 > 其它 >「模擬賽20211006」Disastrous Rain

「模擬賽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\)

,字尾存在一個高度為 \(s\) 的格子,若積水體積為偶數/奇數時的方案總數。

重要的觀察:由於最多平整 \(k\) 個格子,所以說,\(p,s\) 分別最多隻有 \(k+1\) 種取值。所以這個 DP 是 \(O(nk^3)\) 的。


這個複雜度雖然已經很不錯了,但是我們還需要繼續優化。

注意到,最終高度局面中,最高的格子一定不會貢獻任何積水,但是最高的格子一定可以當作邊界來用

例如,中間的黃色格子就是最高的;當我們考慮字首紅格子的時候,我們可以假定後面存在一個更高的格子;同樣的,考慮字尾綠格子的時候,我們也可以假定前面存在一個更高的格子。

因此可以重新設計狀態:設 \(g_{i,j,k,0/1}\)

表示考慮了 \(i\) 個格子之後,字首最大高度為 \(j\),已經使用了 \(k\) 次平整機會,此時積水體積為偶數/奇數的方案總數。正向反向都做一遍,然後列舉最大值合併即可。

列舉最大值需要考慮多個最大值的情況——因此我們總是在列舉第一個最大值。


這個做法還可以優化!

對於所有方案,我們可以將它們劃分為兩類——積水體積為偶數的和積水體積為奇數的。注意到,我們已經知道了兩類方案數的和,我們只需要再知道它們的差即可。因此我們可以拋棄 DP 的最後一維,而直接維護 偶數 - 奇數 即可。

小結:

  1. 注意關鍵性質,例如本題中取值可能性的性質;
  2. 不一定需要從頭 DP 到尾,如果問題有一些特殊的資訊可以列舉,那麼我們可以在 DP 外進行列舉;
  3. 充分利用已知資訊

程式碼

#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;
}