题目:http://www.tsinsen.com/A1357
题意:N棵树砍掉K棵,任意两棵相邻的树至少保留一棵,求方案数,对10007取余 (3<=K<=N<=500)
思路:组合数学问题,n棵树砍k棵,留下(n-k)棵树,就是(n-k)个点之间共有(n-k+1)个(包括两端)空位置,每个空位置都可以插入0或1棵被砍的树,故问题转化为求C(n-k+1,k)
分析可知k<=(n+1)/2
举例6棵树砍3棵
0/1 | 0/1 | 0/1 | 0/1 | |||
树 | 树 | 树 |
直接做求组合数,因为N最大有500,结果非常大,不能乘完后取余
此种解法麻烦的是对10007取余
参考 http://blog.csdn.net/operator456/article/details/38469799
用到
同余定理:
(a*b)%P = (a%P * b%P)%P
(a/b)%P = (a%P * 1/b%P)%P
此题中有除法,用第二个等式
费马小定理
变形后得到:
快速幂取模(a*b%P):把b拆分成2进制表示从右向左一位一位计算,当第n项为1时令前n项的结果乘以a^(2^(n-1)),并实时取余。
#include <stdio.h>
#define MOD 10007
// 快速幂取模
long long exp_mod(int a){
int b = MOD - 2;
long long t = 1;
while (b){
if (b & 1)
t = (t*a) % MOD;
a = (a*a) % MOD;
b >>= 1;
}
return t;
}
// 求组合数
long long zuhe(int a, int b)
{
long long m = 1, n = 1;
long long t = 1;
for (int i = 0; i < b; i++)
{
m = (a - i);
n = (b - i);
t = (t * (m % MOD) * exp_mod(n)) % MOD;
}
return t;
}
int main()
{
int n, k;
while (scanf("%d%d", &n, &k) != EOF)
{
printf("%d\n", zuhe(n - k + 1, k) % MOD);
}
return 0;
}
以后遇到排列组合问题,数字太大取模时可以用此解法
---------------------------------------------------------------------------------------------
此题还有其他解法,我一开始用组合问题分析的,结果算结果的时候取余没有好方法,故放弃,又通过画表分析,得出一个规律,可谓投机取巧。
一直不甘心,在几天之后浏览卡特兰数的解法的时候,看到上文中引用的文章,顿时找到了思路,用 同余定理+费马小定理变形+快速幂取模 解出。很有成就感,特别感谢引用文章的作者。
另外贴出我找规律的做法
n\k | 1 | 2 | 3 | 4 | 5 | 6 |
1 | 1 | 0 | 0 | 0 | 0 | 0 |
2 | 2 | 0 | 0 | 0 | 0 | 0 |
3 | 3 | 1 | 0 | 0 | 0 | 0 |
4 | 4 | 3 | 0 | 0 | 0 | 0 |
5 | 5 | 6 | 1 | 0 | 0 | 0 |
6 | 6 | 10 | 4 | 0 | 0 | 0 |
data[i][j]=data[i-1][j]+data[i-2][j-1]
其实也是运用C(n,c)
#include <stdio.h>
#include <string.h>
short data[501][501];
int main()
{
int n, k;
while (scanf("%d%d", &n, &k) != EOF)
{
memset(data, 0, sizeof(data));
for (int i = 1; i <= 500; i++)
{
data[i][1] = i;
for (int j = 2; j <= (i + 1) / 2; j++)
{
data[i][j] = (data[i - 1][j] + data[i - 2][j - 1]) % 10007;
}
}
printf("%d\n", data[n][k]);
}
return 0;
}
---------------------------------------------------------------------------------------------
此题还可以用动态规划,本人目前正在学习动态规划,对于本题暂时没想出来,稍后会补充。