[容斥 状压DP] Atcoder ARC093 F - Dark Horse

wwwww比赛的时候题目看错了

假设我们确定的1的位置,那么接下来的每一轮,1都会和一段长度为2的幂的区间里,标号最小的人pk。

把1固定在1位置(求出最终方案数后乘上 2n 2 n 就是答案),那么就相当于区间 [2,2] [ 2 , 2 ] [3,4] [ 3 , 4 ] [5,8] [ 5 , 8 ] [2n1+1,2n] [ 2 n − 1 + 1 , 2 n ] 里的最小值不在给出的集合中

考虑容斥,那么就只要求出标号在集合 S S 中的区间的最小值在给出的集合中,其他区间随便放的方案数就可以了

ai a i 从大到小排序,令 fi,S f i , S 表示前 i i 个人,集合 S S 中的区间的最小值在 a a 中的最小值

因为把 ai a i 从大到小排序了,所以每次转移只要算出之前用了多少人,乘个组合数就可以了

#include 
#include 
#include 

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<1][S];
    for(int S=0;S<(1<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<1)],1LL*f[i+1][S]*C(rst,pw[j-1]-1)%P*fac[pw[j-1]]%P);
    }
    }
  }
  for(int i=0;i<(1<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;
}

你可能感兴趣的:(DP,容斥原理,状压DP)