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