2049: 象棋
Description車是中国象棋中的一种棋子,它能攻击同一行或同一列中没有其他棋子阻隔的棋子。一天,小度在棋盘上摆起了许多車……他想知道,在一共N×M个点的矩形棋盘中摆最多个数的車使其互不攻击的方案数。他经过思考,得出了答案。但他仍不满足,想增加一个条件:对于任何一个車A,如果有其他一个車B在它的上方(車B行号小于車A),那么車A必须在車B的右边(車A列号大于車B)。
现在要问问你,满足要求的方案数是多少 。
Input
第一行一个正整数T,表示数据组数。( T<=10)
对于每组数据:一行,两个正整数N和M(N<=100000,M<=100000)。
Output
对于每组数据输出一行,代表方案数模1000000007(10^9+7)。
Sample Input
Sample Output1
1 1
Hint1
思路不难,高中数学组合题,不妨设M>=N,那么最多可以放N个棋子,从M里面挑出N行(列)来,那么根据题设每一种挑的结果都唯一对应一种摆放方案(对于任何一个車A,如果有其他一个車B在它的上方,那么車A必须在車B的右边)。
问题是,碰到这种大数据的组合数我就当场歇菜了,这可咋整啊。。。我这种只会暴力阶乘求组合数以及一点点同余知识的菜鸡求不出来这个啊,这有除法,咋用同余啊。
于是查阅了一下dalao们的解法,学到了好多新名词——乘法逆元,快速幂什么的,稍微研究了一下,好的看不懂。还了解到了1e9+7这个让我懵圈的模数是在这种大数题里面经常会用到的,这有各种好处云云。
终于,看到了一种我能理解的算法,统计阶乘里面各素数因子出现的次数,上下的阶乘可以相减,这样就可以快乐地用同余啦。
int divide(int i, int M)
{
if (a[i] > M) return 0;
return divide(i, M / a[i]) + M / a[i];
}
这个函数是关键所在,借鉴了大佬的写法,巧妙用递归求出M!里面素数i因子的个数。
#include<stdio.h>
#include<math.h>
#define MOD 1000000007
int a[100005];
int T, M, N, P, number;
int setPrime()
{
int count = 1;
a[0] = 2;
a[1] = 3;
for (int i = 4; i <= 100000;i++)
{
int flag = 1;
for (int j = 2; j <= sqrt(i); j++)
{
if (i%j == 0)
{
flag = 0;
break;
}
}
if (flag)
a[++count] = i;
}
return count;
}
int divide(int i, int M)
{
if (a[i] > M) return 0;
return divide(i, M / a[i]) + M / a[i];
}
int main()
{
number=setPrime();
scanf("%d", &T);
while (T--)
{
scanf("%d%d", &M, &N);
if (N > M)
{
P = N;
N = M;
M = P;
}
long long int result=1;
int diff = 0;
for (int i = 0; i <= number; i++)
{
if (a[i] > M) break;
diff = divide(i, M) - divide(i, N) - divide(i, M - N);
while (diff--)
{
result = result * a[i] % MOD;
}
}
printf("%lld\n", result);
}
return 0;
}