hdu5894 hannnnah_j’s Biological Test(2016 acm/icpc 沈阳网络赛,组合数学)

传送门:http://acm.hdu.edu.cn/showproblem.php?pid=5894

题目大意:

有n张桌子围成一圈,坐m个人,任意相邻两人至少隔k个空位。
座位是各不相同的,m个人是相同的,问有多少种组合方法?

题目分析:

设第一个人和第二个人隔了 a1 个空位,第三个人和第二个人隔了 a2 个空位…………第m个人和第一个人隔了 am 个空位,则有:
a1+a2+...+am+m=n ,其中 aik .

根据组合数学公式,解得组合数共有 Cm1nmk1 种。又因为m个人是相同的,而圆圈可以轮换,所以除以m(这里有点不太好理解,因为有人会认为是除以m!,其实是确定第一个人的位置之后转一圈,都是等价的。),然后第一个人有n种坐法,再乘以n。

答案是:
nCm1nmk1m

展开为: n(nmk1)(nmk2)...(nmk(m1))m! .

因为题目中要求这个数mod 1e9+7的值,所以先预处理一下m!对1e9+7的乘法逆元,然后套快速乘法取模就可以了。

这题还有个坑,就是 nmk1 有可能是 m1 的,这种情况是无解的,输出0。然后m=1的时候,不存在相邻两个人的情况,直接输出n。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int M = 1e9+7;
ll pre[1000005];
int t;
ll n,m,k;
int egcd(int a, int b, int &x, int &y) {
  if (!b) {
    x = 1;
    y = 0;
    return a;
  }
  int ans = egcd(b, a % b, x, y);
  int temp = x;
  x = y;
  y = temp - a / b * y;
  return ans;
}
int cal(int a, int m) {//求乘法逆元,即求x 使得ax % M = 1
  int x, y;
  int gcd = egcd(a, m, x, y);
  if (1 % gcd != 0)
    return -1;
  x *= 1 / gcd;
  m = abs(m);
  int ans = x % m;
  if (ans <= 0)
    ans += m;
  return ans;
}
long long qmod( long long a, long long b, long long mod ) {//快速计算 (a*b) % mod

    long long ans = 0;  // 初始化
    while(b)                //根据b的每一位看加不加当前a
    {
        if(b & 1)           //如果当前位为1
        {
            b--;
            ans =(ans+ a)%mod;   //ans+=a
        }
        b /= 2;                         //b向前移位
        a = (a + a) % mod;          //更新a

    }
    return ans;
}
void init() { //预处理1~1e6的阶乘对M的逆元
    pre[1]=1;
    for(int i=2;i<=1e6;i++)
        pre[i]=(pre[i-1]*i)%M;
    for(int i=1;i<=1e6;i++)
        pre[i]=cal(pre[i],M);
}
int main() {
    init();
    scanf("%d",&t);
    while (t--) {
        scanf("%I64d %I64d %I64d",&n,&m,&k);
        if(m==1) {
            printf("%lld\n",n);
            continue;
        }
        if(n-m*k<m) {
            printf("0\n");
            continue;
        }
        ll a = 1;
        for(int i=1;i<m;i++) {
            //cout<<i<<endl;
            a=qmod(a,(n-m*k-i),M);
        }
        a=(a*n)%M;
        a=(a*pre[m])%M;
        printf("%I64d\n", a);
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值