一:传送门
M * N的方格,左上走到右下,
只能向右或向下走。有多少种不同的走法?
由于方法数量可能很大,只需要输出Mod 10^9 + 7的结果。
Input
第1行,2个数M,N,中间用空格隔开。(2 <= m,n <= 1000)
Input
2 3
Output
3
分析:
n, m 都非常小,可以直接打表递推出来。因为只能 向右走 或 向下走,
a[ i ] [ j ] = a[ i - 1 ][ j ] + a[ i ][ j-1 ]; 当前格的方案数 = 上面格的方案数 + 左面格的方案数。
void DP(int m, int n)
{
for(int i = 1; i <= m; i++)
dp[i][1] = 1;
for(int i = 1; i <= n; i++)
dp[1][i] = 1;
for(int i = 2; i <= m; i++)
for(int j = 2; j <= n; j++)
dp[i][j] = (dp[i-1][j] + dp[i][j-1])%M;
}
二:传送门
问题同上,只是 m , n 的数据范围都扩大到了 1e6 ~~~
分析:
显然,用递推,打表,时间过不去,内存也存不下。
对于这个表,观察后可以发现,如果顺时针旋转45°,就是一个杨辉三角形~~(行和列都从 0 开始计数)
对于一个杨辉三角
行数n 和 每行的个数m 都从0开始计数
对于第 n 行 第m个数 , 它的值为 C(n,m)。。。。
右下角的格子,如果看成杨辉三角的话,是 N =(n-1)+ (m-1) 行,第 t =(n-1)或 (m-1) 个数
ans = C( N, t )..
现实理论基础的理解为:::向右 向下 一共可以走 N 步,,从中选择 t 步向下(或向右)。。
所以是C(N,t)。
对于逆元。。。。。。。。。。
取模运算的变形中 是没有除法的
也就是说。。
a/b % c
如果a,b 特别特别大,不能分别计算a%c b%c,
所以要用到逆元转换为乘法来算
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int M = 1e9+7;
void exgcd(int a, int b, int &x, int &y)
{
if(b == 0)
{
x = 1, y = 0;
return ;
}
exgcd(b, a%b, x, y);
int t = x;
x = y;
y = t-a/b*y;
return ;
}
int main()
{
ios::sync_with_stdio(false);
int m,n,x,y;
LL a = 1, b = 1;
cin >> m >> n;
n = n+m-2;
m = m - 1;
for(int i = n; i >= n-m+1; i--)
a = (a*i)%M;
for(int i = m; i >= 1; i--)
b = (b*i)%M;
exgcd(b,M,x,y);
x = (x%M + M) % M; // 确保x为正
LL ans = (a*x)%M;
cout << ans << endl;
return 0;
}
三:传送门
给出一个 n*n 的矩阵, n 的数据范围是 1e9 , 并且不能穿过对角线,
问从左上角到右下角有几种走法。
分析:
不能穿过对角线,且对角线两边是对称的,所以只需要算出一边就行。。
先只看对角线的一边。。只能向下 或者向右运动,经分析,符合卡特兰数。。!!!!!
~~~~~~~~~~~~~~~~~~~~卡特兰数~~~~~~~~~~~~~~~~~
如果一个数列 F( n ) 满足科特兰数, 那么
应用: 对于一个由 n 个 1 和 n 个 0 ,组成的序列,
使 任意的前缀 1 的个数 >= 0的个数。(条件),这样的序列有多少个。。。
1.不考虑条件的情况下,在 2n 个数中选择 n 个位置放 1 ,那么一共有 C( 2n, n ) 种情况。
2.主要就是要减去其中不符合条件的情况。
3.假设放 ( n + 1) 个 0 , ( n - 1) 个1,0的个数比1的个数多2 .。这样必会出现某个前缀,0的个数大于1的个数。
4.假设在某一位,刚好 0 的积累数比 1 的积累数 多 1,那么在这一位的后面的数,0的个数也比1的个数多1,。。
5.如果把 这一位后面的 0 和 1 互换,那么整个序列 0 和 1 的个数就相等了。并且不合法!!!
综上所述:每一个由 ( n+ 1 ) 个0 组成的序列,都对应一个 (n) 个0组成的 不合法序列。
所以不合法序列的个数是 C( 2n, n+1)...
总的合法序列的个数为 : C( 2n, n) - C( 2n, n+1) = C(2n, n) / ( n+1 ) ..
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
而对于本题,n * n 的矩阵 ,把向左走看作 1 , 向下走看作 0,行走的方案就变成了10组成的序列,且10的个数相等。。
如果看对角线上面的部分,第一步肯定是 1,所以问题就转化成了:求 2n长的序列,n个0,n个1,满足任意前缀1
的个数 >= 0的个数的序列的个数,。 最后在 *2 .。就Ok了
这就是卡特兰数了啊····~~~~~!!!!!!!!
这还没有完!!!! 这一题的 n 的范围是 1e9 啊 啊啊啊
如果按照第二题的方法去求组合数就一定会超时的!!!!!!!!
所以就要用超大数求组合数 C( a , b )%M , 卢卡斯定理~~~~~!!!!!
~~~~~~~~~~~~~~~~~~~~Lucas~~~~~~~~~~~~~~~~~
C(a,b)%M,,
在 a , b 很大的情况下,比如1e15......
但是 M 必须是素数,,
而且实际运用中, M 不能很大,,最多1e5吧
不然GetC() 函数还是容易超时,
表达式:C(a ,b)%M = C( a/M , b/M ) * C(a%M ,b%M ) %M...
实际运用中 a/M b/M 还是可能很大的,所以一般用递归算法来求解
LL Lucas(LL a, LL b)
{
if(a < M && b < M)
return C(a,b);
return
C(a%M,b%M) * Lucas(a/M,b/M);
}
因为 M 不是很大,所以组合数就能直接求了
LL GetC(LL a,LL b) //C(a,b)%M
{
if(b > a) return 0;
if(b > a-b) b = a-b; //提高效率
int n = 1, m = 1;
int t = a-b+1;
for(int i = a; i >= t; i--)
m = (m*i)%M;
for(int i = b; i >= 2; i--)
n = (n*i)%M;
// C(a,b)%M = m/n %M
// m,n都已经取过模了,所以要用逆元
exgcd(n,M,x,y);
x = (x%M + M) % M;
return m*x % M;
}
学了卡特兰数 和 卢卡斯定理,这道题就差不多是个模板了,
当然还有一个exgcd 求逆元。这个另外再说。下面是AC代码
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int M = 1e4+7;
void exgcd(LL a, LL b, LL &x, LL &y)
{
if(b == 0)
{
x = 1, y = 0;
return ;
}
exgcd(b, a%b, x, y);
int t = x;
x = y;
y = t-a/b*y;
return ;
}
LL GetC(LL a, LL b)
{
if(b > a-b) b = a-b;
LL x, y;
LL n = 1, m = 1;
LL t = a-b+1;
for(int i = a; i >= t; i--)
m = (m*i)%M;
for(int i = b; i >= 2; i--)
n = (n*i)%M;
exgcd(n,M,x,y);
x = (x%M + M) % M;
return (m*x) % M;
}
LL Lucas(LL a, LL b)
{
if(a < M && b < M)
return GetC(a, b);
return
(GetC(a%M, b%M) * Lucas(a/M, b/M)) % M;
}
int main()
{
ios::sync_with_stdio(false);
LL n,a,b,x,y;
cin >> n;
a = 2*n - 2;
b = n - 1;
// C(a, b) / n
int ans = Lucas(a,b);
// ans/n % M;
exgcd(n,M,x,y);
x = (x%M + M) % M;
cout << 2 * (ans*x) % M << endl;
return 0;
}