1. 程式人生 > 其它 >Solution -「牛客 NOIP 模擬賽」打拳

Solution -「牛客 NOIP 模擬賽」打拳

\(\mathcal{Description}\)

  現\(2^n\) 個人進行淘汰賽,他們的戰力為 \(1\sim 2^n\),戰力強者能戰勝戰力弱者,但是戰力在集合 \(\{a_m\}\) 裡的人可以放水輸給戰力為 \(1\) 的人。求讓 \(1\) 獲勝的初始安排,並要求 \(1\) 依次戰勝的人的戰力序列的 LIS 長度不小於 \(l\)

  \(m\le16\)\(l\le n\le9\)

\(\mathcal{Solution}\)

  出題人你為什麼不把題意寫在題面裡啊?

  第一種方法:列舉 \(1\) 戰勝的 \(n\) 個人的戰力大小關係 DP,非常輕鬆,這裡不提。

  第二種方法需要靈活地聯絡 LIS 的各種求法。結合方法一的一些實現細節,可以發現這樣維護 LIS 非常合理:按值從小到大將數加入序列,維護每個長度的 LIS 的最前結尾位置。首先將 \(\{a_m\}\) 升序排列,定義狀態 \(f(i,S,T)\) 表示考慮了 \(\{a_m\}\) 中前 \(i\) 個數;\(1\) 的戰勝序列中集合 \(S\) 內的位置已經使用;其中集合 \(T\subseteq S\) 內的位置是最優 LIS 的結尾。組合數計算在競標賽樹里加一棵子樹的方案即可轉移。相當於一個 DP 套 DP。複雜度 \(\mathcal O(mn3^n)\)


  所以寫這篇題解的目的是總結一下求 LIS 的方法:

  • \(f(i)\) 表示以 \(i\) 結尾的最長 LIS。優點:好想好寫。
  • \(f(i)\) 表示(考慮了字首位置後)LIS 長度為 \(i\) 的最小結尾數。優點:可將值記入狀態
  • \(f(i)\) 表示(考慮了前若干小的值後)LIS 長度為 \(i\) 的最小結尾位置。優點:可將位置記入狀態

\(\mathcal{Code}\)

/*~Rainybunny~*/

#include <bits/stdc++.h>

#define rep( i, l, r ) for ( int i = l, rep##i = r; i <= rep##i; ++i )
#define per( i, r, l ) for ( int i = r, per##i = l; i >= per##i; --i )

const int MAXN = 9, MAXM = 16;
int n, m, l, P, a[MAXM + 5], f[2][1 << MAXN][1 << MAXN];
int fac[1 << MAXN | 5], ifac[1 << MAXN | 5];

inline void chkmax( int& u, const int v ) { u < v && ( u = v ); }
inline int mul( const int u, const int v ) { return 1ll * u * v % P; }
inline int add( int u, const int v ) { return ( u += v ) < P ? u : u - P; }
inline void addeq( int& u, const int v ) { ( u += v ) >= P && ( u -= P ); }
inline int mpow( int u, int v ) {
	int ret = 1;
	for ( ; v; u = mul( u, u ), v >>= 1 ) ret = mul( ret, v & 1 ? u : 1 );
	return ret;
}

inline void init() {
	fac[0] = 1;
	rep ( i, 1, 1 << n ) fac[i] = mul( i, fac[i - 1] );
	ifac[1 << n] = mpow( fac[1 << n], P - 2 );
	per ( i, ( 1 << n ) - 1, 0 ) ifac[i] = mul( i + 1, ifac[i + 1] );
}

inline int bino( const int u, const int v ) {
	return v < 0 || u < v ? 0 : mul( fac[u], mul( ifac[v], ifac[u - v] ) );
}

int main() {
	freopen( "punch.in", "r", stdin );
	freopen( "punch.out", "w", stdout );
	
	scanf( "%d %d %d %d", &n, &m, &l, &P ), init();
	rep ( i, 1, m ) scanf( "%d", &a[i] );
	std::sort( a + 1, a + m + 1 );
	
	f[0][0][0] = 1;
	rep ( i, 1, m ) {
		int sta = ~i & 1;
		rep ( S, 0, ( 1 << n ) - 1 ) for ( int T = S; ; T = ( T - 1 ) & S ) {
			int& cur = f[sta][S][T];
			if ( !cur && !T ) break;
			if ( !cur ) continue;
			
			addeq( f[!sta][S][T], cur );
			rep ( j, 0, n - 1 ) if ( ~S >> j & 1 ) {
				int c = mul( fac[1 << j],
				  bino( a[i] - 2 - S, ( 1 << j ) - 1 ) );
				if ( !c ) continue;
				
				addeq( f[!sta][S | 1 << j][( T >> j ?
				  ( T >> j & ( T >> j ) - 1 ) << j |
				  T & ( 1 << j ) - 1 : T ) | 1 << j], mul( cur, c ) );
			}
			
			cur = 0;
			if ( !T ) break;
		}
	}
	
	int ans = 0;
	rep ( T, 0, ( 1 << n ) - 1 ) if ( __builtin_popcount( T ) >= l ) {
		addeq( ans, f[m & 1][( 1 << n ) - 1][T] );
	}
	printf( "%d\n", mul( ans, 1 << n ) );
	return 0;
}