Lucas定理:用于求C(n,m) mod p,其中p为素数
证明等在网上都可以找到,我也不是很懂就略过了(懂了补上)。
直接贴出用法吧:
主要代码就两行,需要用到的知识有快速幂和求逆元(计算组合数),必要的时候需要打表(计算阶乘)
快速幂:https://blog.csdn.net/Radium_1209/article/details/79718918
核心代码:
ll lucas(ll a,ll b,ll p)
{
if (b==0) return 1;
//C是求组合数的函数
return C(a%p,b%p,p)*lucas(a/p,b/p,p);
}
具体应用(关键:求组合数时要用到快速幂和求逆元)
例题1(不用打表的):
Problem 2020 组合(FZU2020)
Accept: 1614 Submit: 3822
Time Limit: 1000 mSec Memory Limit : 32768 KB
Problem Description
给出组合数C(n,m), 表示从n个元素中选出m个元素的方案数。例如C(5,2) = 10, C(4,2) = 6.可是当n,m比较大的时候,C(n,m)很大!于是xiaobo希望你输出 C(n,m) mod p的值!
Input
输入数据第一行是一个正整数T,表示数据组数 (T <= 100) 接下来是T组数据,每组数据有3个正整数 n, m, p (1 <= m <= n <= 10^9, m <= 10^4, m < p < 10^9, p是素数)
Output
对于每组数据,输出一个正整数,表示C(n,m) mod p的结果。
Sample Input
2 5 2 3 5 2 61
Sample Output
1 10
Source
题意:很简单就直接用Lucas定理就行,也不会超时
#include <cstdio>
#include <iostream>
typedef long long ll;
using namespace std;
//快速幂
ll pow_mod(ll a,ll x,ll p)
{
ll ret=1;
while(x)
{
if (x&1) ret=ret*a%p;
a=a*a%p;
x>>=1;
}
return ret;
}
//求组合数
ll C(ll n,ll m,ll p)
{
if (m==0) return 1;
if (m>n-m) m=n-m;
ll up=1,down=1;
//求分子分母
for (int i=1;i<=m;i++)
{
up=(up*(n-i+1))%p;
down=(down*i)%p;
}
//利用快速幂求逆元,其中pow_mod(down,p-2,p)是逆元
return up*pow_mod(down,p-2,p)%p;
}
//lucas定理
ll lucas(ll a,ll b,ll p)
{
if (b==0) return 1;
return C(a%p,b%p,p)*lucas(a/p,b/p,p);
}
int main()
{
ll n,m,p,T;
cin >> T;
while(T--)
{
cin >> n >> m >> p;
cout << lucas(n,m,p) << endl;
}
return 0;
}
例题2(需要打表):
DP?(HDU3944)
Time Limit: 10000/3000 MS (Java/Others) Memory Limit: 128000/128000 K (Java/Others)
Total Submission(s): 3897 Accepted Submission(s): 1247
Problem Description
Figure 1 shows the Yang Hui Triangle. We number the row from top to bottom 0,1,2,…and the column from left to right 0,1,2,….If using C(n,k) represents the number of row n, column k. The Yang Hui Triangle has a regular pattern as follows.
C(n,0)=C(n,n)=1 (n ≥ 0)
C(n,k)=C(n-1,k-1)+C(n-1,k) (0<k<n)
Write a program that calculates the minimum sum of numbers passed on a route that starts at the top and ends at row n, column k. Each step can go either straight down or diagonally down to the right like figure 2.
As the answer may be very large, you only need to output the answer mod p which is a prime.
Input
Input to the problem will consists of series of up to 100000 data sets. For each data there is a line contains three integers n, k(0<=k<=n<10^9) p(p<10^4 and p is a prime) . Input is terminated by end-of-file.
Output
For every test case, you should output "Case #C: " first, where C indicates the case number and starts at 1.Then output the minimum sum mod p.
Sample Input
1 1 2 4 2 7
Sample Output
Case #1: 0 Case #2: 5
Author
phyxnj@UESTC
Source
题意:给你一个杨辉三角,让你求弄顶部到某一位置的最短路径(只能向下和右下走)。大胆猜想可以一直向下然后一直想右下走,或者一直向右下然后一直向下,此时路径最短。这两种情况可以综合来考虑,考虑到数据范围猜想可能可以化简公式,经队友提示队友得到公式为C(n+1,k)+n-k,对于k+1>n/2时转换成向下走的情况即,k=n-k。
注意:若不打表直接计算组合数可能会超时,由于p较小,可以预处理所以p的情况,进行打表。
#include <cstdio>
#include <iostream>
#include <cstring>
#define MAX_N 10005
typedef long long ll;
using namespace std;
int cnt=0,sum=0;
ll n,p,k;
ll c[MAX_N][MAX_N];
ll prime[MAX_N],flag[MAX_N];
ll pos[MAX_N];
//快速幂
ll pow_mod(ll a,ll x,ll p)
{
ll ret=1;
while(x)
{
if (x&1) ret=ret*a%p;
a=a*a%p;
x>>=1;
}
return ret;
}
//打表
void init()
{
memset(flag,0,sizeof(flag));
//求出所有素数存入prime,用pos记录位置
for (int i=2;i<=10000;i++)
{
if (!flag[i])
{
prime[sum]=i;
//printf("%lld\n",prime[sum]);
pos[i]=sum++;
}
for (int j=i;j<=10000;j+=i)
flag[j]=1;
}
//计算对于p是i的阶乘
for (int i=0;i<sum;i++)
{
c[i][0]=1; c[i][1]=1;
for (int j=2;j<=prime[i];j++)
c[i][j]=(c[i][j-1]*j)%prime[i];
}
}
//求组合数
ll C(ll n,ll m,ll p)
{
if (n<m) return 0;
//利用逆元已经已经求得的阶乘计算组合数
return c[pos[p]][n]*pow_mod(c[pos[p]][m]*c[pos[p]][n-m],p-2,p)%p;
}
//还是两行调用lucas
ll lucas(ll a,ll b,ll p)
{
if (b==0) return 1;
else return C(a%p,b%p,p)*lucas(a/p,b/p,p);
}
int main()
{
init();
while(~scanf("%lld%lld%lld",&n,&k,&p))
{
//将两种情况合并为一种的操作,注意不可少
if (k+1>n/2) k=n-k;
printf("Case #%d: %lld\n",++cnt,(lucas(n+1,k,p)+n-k)%p);
}
return 0;
}