Solution -「牛客 NOIP 模擬賽」打拳
阿新 • • 發佈:2021-10-11
\(\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; }