1. 程式人生 > 其它 >[提高組集訓2021] 一拳超人

[提高組集訓2021] 一拳超人

一、題目

看到這個題目我想起了巨集帆機房牆上那個洞\(...\)以後回去參觀那個著名景點

著名拳擊擂臺"妹妹"的拳擊比賽開賽了,一共有 \(2^n\) 個選手,我們把所有選手的實力用 \(1\)\(2^n\) 的一個排列表示。哥哥 \(\tt zxy\) 混入了其中,他的實力值為 \(1\)

真正的比賽順序用一個排列表示,相鄰兩個人比賽決出勝者之後進入下一輪。勝者原則上是實力較強的那個人,但是 \(\tt zxy\) 買通了 \(m\) 個妹妹(給出他們的能力值),如果 \(\tt zxy\) 遇到了她們那麼會獲得勝利。

\(\tt zxy\) 最終想贏得場比賽,並且他想讓戰勝序列(把戰勝的人按順序取出)的最長上升子序列不超過 \(k\)

,問方案數模 \(mod\) 的大小。

\(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\)

,那麼方案數是:

\[\sum_{i=1}^{n} {b_i-2-\sum_{j=1}^{i-1} q_j\choose q_i-1} \]

那麼我們可以將買通的妹妹排序,然後規劃一個長度為 \(n\) 的子序列出來,方案數是可以在過程中計算的。

現在考慮怎麼在規劃過程中維護最長上升子序列,考慮那個 \(\tt set\) 求最長上升子序列的方法,只是現在我們按值有序來做的。設 \(g_i\) 表示長度為 \(i\) 的最長上升子序列的最小結尾位置,設新插入的一個位置是 \(j\),那麼把數位 \(j\) 後面第一個 \(1\) 平移到這一位即可(如果沒有直接增加)

所以設 \(dp[i][s][t]\)

表示前 \(i\) 個數,已經確定的位置集合是 \(s\)\(\tt lcs\) 的狀壓是 \(t\) 的方案數,時間複雜度 \(O(nm3^n)\)(因為 \(t\) 實際上是 \(s\) 的一個子集,但你怎麼些都沒關係的)

如果你不會最後那個 \(\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);
}