一、题目
看到这个题目我想起了宏帆机房墙上那个洞(...)(以后回去参观那个著名景点)
著名拳击擂台"妹妹"的拳击比赛开赛了,一共有 (2^n) 个选手,我们把所有选手的实力用 (1) 到 (2^n) 的一个排列表示。哥哥 ( t zxy) 混入了其中,他的实力值为 (1)
真正的比赛顺序用一个排列表示,相邻两个人比赛决出胜者之后进入下一轮。胜者原则上是实力较强的那个人,但是 ( t zxy) 买通了 (m) 个妹妹(给出他们的能力值),如果 ( t zxy) 遇到了她们那么会获得胜利。
( t zxy) 最终想赢得场比赛,并且他想让战胜序列(把战胜的人按顺序取出)的最长上升子序列不超过 (k),问方案数模 (mod) 的大小。
(kleq nleq 9,1leq mleq 16,10^8leq modleq 10^9+7)
二、解法
我们假定主角在一号位置,最后把答案乘上 (2^n) 即可。
我本来直接按顺序规划第一个人遇到哪些对手,但是这样方案数会难算到爆,记录的状态也会变多,究其原因是每一次算对手的方案数时不知道哪些人已经使用过了,所以很难算。
那么此时改变 (dp) 顺序,假设我们遇到人的顺序是 (a_1,a_2...a_n),排序之后的顺序是 (b_1,b_2...b_n),设排序之后第 (i) 个人和主角打需要花费人的个数(她作为子树内的最大值)为 (q_i),那么方案数是:
那么我们可以将买通的妹妹排序,然后规划一个长度为 (n) 的子序列出来,方案数是可以在过程中计算的。
现在考虑怎么在规划过程中维护最长上升子序列,考虑那个 ( t set) 求最长上升子序列的方法,只是现在我们按值有序来做的。设 (g_i) 表示长度为 (i) 的最长上升子序列的最小结尾位置,设新插入的一个位置是 (j),那么把数位 (j) 后面第一个 (1) 平移到这一位即可(如果没有直接增加)
所以设 (dp[i][s][t]) 表示前 (i) 个数,已经确定的位置集合是 (s),( t lcs) 的状压是 (t) 的方案数,时间复杂度 (O(nm3^n))(因为 (t) 实际上是 (s) 的一个子集,但你怎么些都没关系的)
如果你不会最后那个 ( t lcs) 的方法,那么暴力枚举打败序列的离散顺序也是可以算出来的。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int M = 600;
#define int long long
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,k,mod,ans,C[M][M],fac[M],b[20],dp[2][1<<9][1<<9];
void init(int n)
{
fac[0]=C[0][0]=1;
for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod;
for(int i=1;i<=n;i++)
{
C[i][0]=1;
for(int j=1;j<=i;j++)
C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
}
}
void add(int &x,int y) {x=(x+y)%mod;}
signed main()
{
freopen("punch.in","r",stdin);
freopen("punch.out","w",stdout);
n=read();m=read();k=read();mod=read();
init(1<<n);
for(int i=1;i<=m;i++)
b[i]=read();
sort(b+1,b+1+m);
int nw=0;dp[0][0][0]=1;
for(int i=1;i<=m;i++)
{
nw^=1;
memset(dp[nw],0,sizeof dp[nw]);
for(int s=0;s<(1<<n);s++)
for(int t=0;t<(1<<n);t++) if(dp[nw^1][s][t])
{
add(dp[nw][s][t],dp[nw^1][s][t]);
for(int j=0;j<n;j++) if(!(s>>j&1) && !(t>>j&1))
{
int up=t>>(j+1);up=up&(up-1);
int to=(up<<(j+1))|(1<<j)|(t&((1<<j)-1));
if(b[i]-2-s<(1<<j)-1) continue;//illegal
add(dp[nw][s|(1<<j)][to],dp[nw^1][s][t]
*fac[1<<j]%mod*C[b[i]-2-s][(1<<j)-1]);
}
}
}
for(int s=0;s<(1<<n);s++)
if(__builtin_popcount(s)>=k)
add(ans,dp[nw][(1<<n)-1][s]);
printf("%lld
",ans*(1<<n)%mod);
}