wwwww比赛的时候题目看错了
假设我们确定的1的位置,那么接下来的每一轮,1都会和一段长度为2的幂的区间里,标号最小的人pk。
把1固定在1位置(求出最终方案数后乘上 2n 2 n 就是答案),那么就相当于区间 [2,2] [ 2 , 2 ] , [3,4] [ 3 , 4 ] , [5,8] [ 5 , 8 ] … [2n−1+1,2n] [ 2 n − 1 + 1 , 2 n ] 里的最小值不在给出的集合中
考虑容斥,那么就只要求出标号在集合 S S 中的区间的最小值在给出的集合中,其他区间随便放的方案数就可以了
把 从大到小排序,令 fi,S f i , S 表示前 i i 个人,集合 中的区间的最小值在 a a 中的最小值
因为把 从大到小排序了,所以每次转移只要算出之前用了多少人,乘个组合数就可以了
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
const int N=20,P=1e9+7;
int n,m,p,a[N],f[N][1<<16|5],fac[1<<16|5],inv[1<<16|5];
inline int C(int x,int y){
if(x<y) return 0;
return 1LL*fac[x]*inv[y]%P*inv[x-y]%P;
}
int pw[N];
inline void add(int &x,int y){
(x+=y)%=P;
}
int main(){
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
scanf("%d%d",&n,&m);
pw[0]=1; for(int i=1;i<=n;i++) pw[i]=pw[i-1]*2; p=pw[n];
fac[0]=1; for(int i=1;i<=p;i++) fac[i]=1LL*fac[i-1]*i%P;
inv[1]=1; for(int i=2;i<=p;i++) inv[i]=1LL*(P-P/i)*inv[P%i]%P;
inv[0]=1; for(int i=1;i<=p;i++) inv[i]=1LL*inv[i]*inv[i-1]%P;
for(int i=1;i<=m;i++) scanf("%d",&a[i]);
sort(a+1,a+1+m); int ans=0; f[m+1][0]=1;
for(int i=m;i;i--){
for(int S=0;S<(1<<n);S++) f[i][S]=f[i+1][S];
for(int S=0;S<(1<<n);S++){
int cnt=0;
for(int j=1;j<=n;j++)
if(S>>(j-1)&1) cnt+=pw[j-1];
int rst=p-a[i]-cnt;
for(int j=1;j<=n;j++)
if(S>>(j-1)&1);else{
if(pw[j-1]-1>rst) continue;
add(f[i][S|(1<<j-1)],1LL*f[i+1][S]*C(rst,pw[j-1]-1)%P*fac[pw[j-1]]%P);
}
}
}
for(int i=0;i<(1<<n);i++){
int cur=f[1][i],cnt=0,tot=0;
for(int j=1;j<=n;j++)
if((i>>(j-1))&1) cnt+=pw[j-1],tot++;
cur=1LL*cur*fac[p-cnt-1]%P;
if(tot&1) ans=(ans-cur)%P;
else ans=(ans+cur)%P;
}
ans=1LL*ans*p%P;
printf("%d\n",(ans+P)%P);
return 0;
}