[数学记录]AT3981 [ARC093D] Dark Horse

command_block

2021-02-14 13:11:12

Personal

**题意** : 有 $2^n$ 个人,按照满二叉树的形态进行淘汰赛,一开始的排列顺序为所有 $(2^n)!$ 个排列之一。 你是第 $1$ 个人,已知每一对人之间的实力关系,具体地说: - 给出 $m$ 个人 $A_1 \sim A_m$。 - 这 $m$ 个人都打得过你。 - 你打得过除了这 $m$ 个人之外的所有其他人。 - 对于剩下的情况(你不参与的情况),编号小的人胜利。 问你在所有的 $(2^n)!$ 种情况中,有多少种情况可以取得最终胜利。答案对 $10^9 + 7$ 取模。 $n,m\leq 16$ ,时限$\texttt{2s}$。 ------------ 将比 $1$ 强的 $m$ 个人称为大佬。 首先将 $1$ 放在第一个位置,最后将答案乘以 $2^n$。 每个人都只会参与恰好 $n$ 场对局,而你只需要保证这 $n$ 场中没有遇到大佬。 各个对局分别对应大小为 $1,2,4,\dots 2^{n-1}$ 的子树。 考虑容斥。钦定对局集合 $S$,则要在对应子树中安排选手,使得被钦定的子树的最小值是大佬。 记方案数为 $g(S)$ ,则答案为 $2^n\sum\limits_{S}(-1)^{|S|}g(S)$。 考虑状压 $\rm DP$ ,按照编号从大到小的顺序考虑大佬(因为编号越大限制越强)。 设 $f[i,s]$ 为考虑了前 $i$ 个大佬,且子树集合 $s$ 被覆盖的贡献和。 在安排第 $i$ 个大佬时,可以不加入,也可以处理某个子树。 记 $siz(s)=\sum\limits_{j\in s}2^j$ ,即已经被安排好的子树大小和。 转移 : $f[i,s]=f[i-1,s]-\sum\limits_{j\in s}f[i-1,s-\{j\}]\big(2^n-A_i-siz(s-\{j\})\big)^{\underline {2^j-1}}2^j$ 其中 $\big(2^n-A_i-siz(s-\{j\})\big)^{\underline {2^j-1}}$ 是选出未被占用的比 $A_i$ 大的 $2^j-1$ 个垫背者,最后将 $A_i$ 加入有 $2^j$ 种方法。 其中下降幂可以预处理阶乘后快速计算。 边界 : $f[0,\varnothing]=1$ 最终需要考虑剩余数的排列,有 $g(s)=f[n,s]\big(2^n-1-siz(s)\big)!$ 复杂度 $O(nm2^n)$。(所以其实 $m$ 大一点也没关系的?) ```cpp #include<algorithm> #include<cstdio> #define ll long long #define MaxS 66000 using namespace std; const int mod=1000000007; ll powM(ll a,int t=mod-2){ ll ret=1; while(t){ if (t&1)ret=ret*a%mod; a=a*a%mod;t>>=1; }return ret; } ll fac[MaxS],ifac[MaxS]; ll dpow(int k,int n) {return k<n ? 0 : fac[k]*ifac[k-n]%mod;} void Init(int n) { fac[0]=1; for (int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod; ifac[n]=powM(fac[n]); for (int i=n;i;i--) ifac[i-1]=ifac[i]*i%mod; } ll f[18][MaxS]; int cnt(int s){ int ret=0; while(s){s^=s&-s;ret++;} return ret; } int n,m,a[18]; int main() { scanf("%d%d",&n,&m); int tot=1<<n;Init(tot); for (int i=1;i<=m;i++)scanf("%d",&a[i]); reverse(a+1,a+m+1); f[0][0]=1; for (int i=1;i<=m;i++) for (int s=0;s<tot;s++){ f[i][s]=f[i-1][s]; for (int j=0;j<n;j++) if (s&(1<<j)) f[i][s]=(f[i][s]+f[i-1][s^(1<<j)]*dpow(tot-a[i]-(s^(1<<j)),(1<<j)-1)%mod*(1<<j))%mod; } ll ans=0; for (int s=0;s<tot;s++){ ll buf=f[m][s]*fac[tot-1-s]%mod; if (cnt(s)&1)ans-=buf;else ans+=buf; }ans=(ans%mod+mod)%mod; printf("%lld",(ans<<n)%mod); return 0; } ```