• [提高组集训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);
    }
    
  • 相关阅读:
    CF1095F Make It Connected
    【ZJOI2016】旅行者
    [COCI 2009] OTOCI / 极地旅行社
    Sum in the tree
    MST Unification
    【集训队作业2018】Simple Tree
    [COCI2009]Dvapravca
    python 网络编程-02 多进程的交互案例
    python 日志模块logging
    python 网络编程-01基础
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/15390544.html
Copyright © 2020-2023  润新知