[提高组集训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\),那么方案数是:

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

那么我们可以将买通的妹妹排序,然后规划一个长度为 \(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);
}
posted @ 2021-10-10 20:34  C202044zxy  阅读(621)  评论(1编辑  收藏  举报