洛谷P3270:成绩比较(容斥、组合数学)

本文解析了一道涉及组合数学的编程竞赛题目,通过详细的步骤解释了如何计算特定条件下的排列组合数量,包括选择人员的方案数及学科分数的分布情况。

解析

依然不会亚qwq
但这次至少有点上道了
(指推出了一个会导致重复计数的错误式子)

首先,我们要选出碾压那些人,方案数就是Cn−1kC_{n-1}^kCn1k

然后,我们要统计每门学科的排名情况
考虑比B神分数高的人一定是从没被碾压的人里选。所以对应的方案就是Cn−k−1ri−1C_{n-k-1}^{r_i-1}Cnk1ri1,设为fkf_kfk
但是这样随便选可能会导致有的应该没被碾压的人一直没被选到,被碾压,所以这个东西其实是至少碾压kkk人的方案数
那么使用常规的容斥套路,这部分的答案就是fk−fk+1+fk+2−...f_k-f_{k+1}+f_{k+2}-...fkfk+1+fk+2...

最后,我们要统计每门学科的分数情况
gu,a,bg_{u,a,b}gu,a,b表示总分数为u,B神前面有a个人,后面有b个人的方案数
枚举B神的分数 i,那么就有:
gu,a,b=∑i−1u(u−i)a∗ibg_{u,a,b}=\sum_{i-1}^u(u-i)^a*i^bgu,a,b=i1u(ui)aib
但是这个东西暴力算是On的
(PS:我就是卡在这里了)qwq

考虑由于a和b非常少,我们可以使用离散化的思路
设n个人的分数一共有t个取值
枚举t,就有:
gu,a,b=∑t=1ngt,a,b×Cutg_{u,a,b}=\sum_{t=1}^ng_{t,a,b}\times C_u^tgu,a,b=t=1ngt,a,b×Cut
里面的 g 可以暴力求解
问题得以解决

代码

#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
#define ll long long
#define il inline
il ll read(){
  ll x=0,f=1;char c=getchar();
  while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
  while(isdigit(c)){x=x*10+c-'0';c=getchar();}
  return x*f;
}
int n,m,k;
ll c[105][105],u[105],r[105],mx=1000;
ll sum[105],jc[105],ni[105];
inline ll ksm(ll x,ll k){
  ll res=1;
  while(k){
    if(k&1) res=res*x%mod;
    x=x*x%mod;
    k>>=1;
  }
  return res;
}
ll f[105][105];

ll D[105];
inline ll g(ll u,ll a,ll b){
  ll res(0);
  for(int i=1;i<=u;i++){
    res+=f[u-i][a]*f[i][b];
    res%=mod;
  }
  return res;
}
ll G(ll u,ll a,ll b){
  ll C=1,ans=0;
  //printf("\nG:u=%lld a=%lld b=%lld\n",u,a,b);
  for(int t=1;t<=n;t++){
    ll now=g(t,a,b);
    for(int i=1;i<t;i++) now=(now-D[i]*c[t][i]%mod+mod)%mod;
    D[t]=now;
    C=C*(u-t+1)%mod*jc[t-1]%mod*ni[t]%mod;
    ans+=now*C;
    ans%=mod;
    //printf("  t=%d C=%lld now=%lld ans=%lld\n",t,C,D[t],ans);
  }
  return ans;
}

int main(){
#ifndef ONLINE_JUDGE
  freopen("a.in","r",stdin);
  freopen("a.out","w",stdout);
  #endif
  n=read();m=read();k=read();
  for(int i=1;i<=m;i++) u[i]=read();
  for(int i=1;i<=m;i++) r[i]=read(),mx=min(mx,n-r[i]);
  
  jc[0]=1;
  for(int i=1;i<=100;i++) jc[i]=jc[i-1]*i%mod;
  ni[n]=ksm(jc[n],mod-2);
  for(int i=n-1;i>=0;i--) ni[i]=ni[i+1]*(i+1)%mod;
  for(int i=0;i<=100;i++){
    f[i][0]=1;
    for(int j=1;j<=100;j++) f[i][j]=f[i][j-1]*i%mod;
  }
  c[0][0]=1;
  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;
    }
  }
  for(int i=1;i<=m;i++){
    sum[i]=G(u[i],r[i]-1,n-r[i]);
    //printf("i=%d sum=%lld\n",i,sum[i]);
  }
  ll ans=0;
  for(int o=k;o<=n;o++){
    ll now=c[n-k-1][n-o-1];
    //printf("  i=0 now=%lld\n",now);
    for(int i=1;i<=m;i++){
      //now=now*c[n-o-1][r[i]-1]%mod;
      now=now*c[n-o-1][r[i]-1]%mod*sum[i]%mod;
      //printf("  i=%d now=%lld\n",i,now);
    }
    if((o-k)&1){
      ans-=now;
      if(ans<0) ans+=mod;
    }
    else{
      ans+=now;
      if(ans>=mod) ans-=mod;
    }
    //printf("o=%d now=%lld ans=%lld\n\n",o,now,ans);
    //printf("o=%d now=%lld\n",o,now);
  }
  //for(int i=1;i<=m;i++){
    //ans=ans*sum[i]%mod;
    //printf("i=%d ans=%lld\n",i,ans);
  //}
  printf("%lld\n",ans*c[n-1][k]%mod);
  return 0;
}
/*

 */

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值