传送门:http://acm.hdu.edu.cn/showproblem.php?pid=5894
题目大意:
有n张桌子围成一圈,坐m个人,任意相邻两人至少隔k个空位。
座位是各不相同的,m个人是相同的,问有多少种组合方法?
题目分析:
设第一个人和第二个人隔了
a1
个空位,第三个人和第二个人隔了
a2
个空位…………第m个人和第一个人隔了
am
个空位,则有:
a1+a2+...+am+m=n
,其中
ai≥k
.
根据组合数学公式,解得组合数共有 Cm−1n−mk−1 种。又因为m个人是相同的,而圆圈可以轮换,所以除以m(这里有点不太好理解,因为有人会认为是除以m!,其实是确定第一个人的位置之后转一圈,都是等价的。),然后第一个人有n种坐法,再乘以n。
答案是:
nCm−1n−mk−1m
展开为: n(n−mk−1)(n−mk−2)...(n−mk−(m−1))m! .
因为题目中要求这个数mod 1e9+7的值,所以先预处理一下m!对1e9+7的乘法逆元,然后套快速乘法取模就可以了。
这题还有个坑,就是 n−mk−1 有可能是 ≤m−1 的,这种情况是无解的,输出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);
}
}