[提高组集训2021] 一拳超人

一、题目

看到这个题目我想起了宏帆机房墙上那个洞(...)以后回去参观那个著名景点

著名拳击擂台"妹妹"的拳击比赛开赛了,一共有 (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),那么方案数是:

[sum_{i=1}^{n} {b_i-2-sum_{j=1}^{i-1} q_jchoose q_i-1} ]

那么我们可以将买通的妹妹排序,然后规划一个长度为 (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);
}
原文地址:https://www.cnblogs.com/C202044zxy/p/15390544.html