Lucas定理(求组合数,例题FZU2020,HDU3944)

6 篇文章 0 订阅
3 篇文章 0 订阅

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

FZU2020

题意:很简单就直接用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

HDU3944

题意:给你一个杨辉三角,让你求弄顶部到某一位置的最短路径(只能向下和右下走)。大胆猜想可以一直向下然后一直想右下走,或者一直向右下然后一直向下,此时路径最短。这两种情况可以综合来考虑,考虑到数据范围猜想可能可以化简公式,经队友提示队友得到公式为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;
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值