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

本文解析了一个比赛中的特定算法问题,通过固定关键元素位置并利用容斥原理来计算可能的方案数量。详细介绍了如何通过排序和组合数学的方法求解,并提供了完整的C++代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

wwwww比赛的时候题目看错了

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

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

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

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

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

#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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值