[提高組集訓2021] 一拳超人
一、題目
看到這個題目我想起了巨集帆機房牆上那個洞\(...\)(以後回去參觀那個著名景點)
著名拳擊擂臺"妹妹"的拳擊比賽開賽了,一共有 \(2^n\) 個選手,我們把所有選手的實力用 \(1\) 到 \(2^n\) 的一個排列表示。哥哥 \(\tt zxy\) 混入了其中,他的實力值為 \(1\)
真正的比賽順序用一個排列表示,相鄰兩個人比賽決出勝者之後進入下一輪。勝者原則上是實力較強的那個人,但是 \(\tt zxy\) 買通了 \(m\) 個妹妹(給出他們的能力值),如果 \(\tt zxy\) 遇到了她們那麼會獲得勝利。
\(\tt zxy\) 最終想贏得場比賽,並且他想讓戰勝序列(把戰勝的人按順序取出)的最長上升子序列不超過 \(k\)
\(k\leq n\leq 9,1\leq m\leq 16,10^8\leq mod\leq 10^9+7\)
二、解法
我們假定主角在一號位置,最後把答案乘上 \(2^n\) 即可。
我本來直接按順序規劃第一個人遇到哪些對手,但是這樣方案數會難算到爆,記錄的狀態也會變多,究其原因是每一次算對手的方案數時不知道哪些人已經使用過了,所以很難算。
那麼此時改變 \(dp\) 順序,假設我們遇到人的順序是 \(a_1,a_2...a_n\),排序之後的順序是 \(b_1,b_2...b_n\),設排序之後第 \(i\) 個人和主角打需要花費人的個數(她作為子樹內的最大值)為 \(q_i\)
那麼我們可以將買通的妹妹排序,然後規劃一個長度為 \(n\) 的子序列出來,方案數是可以在過程中計算的。
現在考慮怎麼在規劃過程中維護最長上升子序列,考慮那個 \(\tt set\) 求最長上升子序列的方法,只是現在我們按值有序來做的。設 \(g_i\) 表示長度為 \(i\) 的最長上升子序列的最小結尾位置,設新插入的一個位置是 \(j\),那麼把數位 \(j\) 後面第一個 \(1\) 平移到這一位即可(如果沒有直接增加)
所以設 \(dp[i][s][t]\)
如果你不會最後那個 \(\tt 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\n",ans*(1<<n)%mod);
}