DP?(数论+组合数学综合题:组合数性质+预处理+组合数取摸)

171 篇文章 0 订阅
79 篇文章 0 订阅


Link:http://acm.hdu.edu.cn/showproblem.php?pid=3944


DP?

Time Limit: 10000/3000 MS (Java/Others)    Memory Limit: 128000/128000 K (Java/Others)
Total Submission(s): 2515    Accepted Submission(s): 782


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
 

题意:给出杨辉三角数塔模型,求从顶部到第n行第k列的元素所经过的路径最小和。

编程思想:
这里用到了组合数的性质公式:C(n,k)=C(n-1,k-1)+C(n-1,k)   。由题意得到的规律:从目标点出发,每次都往左上方走,然后,走到第0列的时候就往上一直走(全为1),即最小值。故答案为 C(n,k)+C(n-1,k-1)+……+C(n-k+1,1)+C(n-k,0)+C(n-k-1,0)+……C(0,0);若按得出的公式直接Lucas算,则TLE。。必须利用组合数的性质进行预处理。把C(n-k,0)写成C(n-k+1,0)(因为都是1!),这样,上面式子那一段,可以从尾到头依次合并,最终合并为 C(n+1,k)。所以这里只要求一次组合数就可以了。 预处理技巧: 定义一个数组,所有小于10000的数的阶乘对所有小于10000的的素数进行取模,这样到lucas定理计算的时候就可以直接用了。
详见代码注释。

AC code:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<queue>
#include<map>
#include<stack>
#include<vector>
#define LL long long
#define MAXN 1000010
using namespace std;
const  int N=20;//模方程数 
const int maxx=10005;//最大素数上限 
LL a[N],mod[N];
int frc[1300][maxx];//frc[i][j]表示(j!)%(第i个素数prim[i])
int id[maxx];//素数编号 
bool vis[maxx];
int prim[maxx];//存储素数
int cnt;//素数个数
void init()//素数筛选及打表求:(j!)%(第i个素数prim[i]) 
{
	for(int i=2;i<maxx;i++) if(!vis[i]){
		prim[cnt]=i;
		id[i]=cnt;
		for(int j=i*2;j<maxx;j+=i)vis[j]=1;
		frc[cnt][0]=1;
		for(int j=1;j<prim[cnt];j++) frc[cnt][j]=frc[cnt][j-1]*j%prim[cnt];
		cnt++;
	}
} 
/*LL mul(LL a,LL b,LL mod)//a*b%mod
{
	LL ans=0;
	while(b){
		if(b&1)
			ans=(ans+a)%mod;
		b>>=1;
		a=(a+a)%mod;
	}
	return ans;
}*/

LL quick_mod(LL a,LL b,LL m)//a^b%m 
{
	LL ans=1;
	a%=m;
	while(b)
	{
		if(b&1)
		{
			ans=ans*a%m;
		}			
		b>>=1;
		a=a*a%m;
	}
	return ans;
}

LL getC(LL n,LL m,int cur)//C(n,m)%mod[cur]
{
	LL p=mod[cur];//p为要取的素数模mod[cur] 
	if(m>n)
		return 0;
	if(m>n-m)
		m=n-m;
	LL ans=1;
/*	for(int i=1;i<=m;i++)
	{
		LL a=(n+i-m)%p;
		LL b=i%p;
		//ans=mul(ans,mul(a,quick_mod(b,p-2,p),p),p);//p为素数,i对p的逆元可以不用扩张欧几里得进行求解  re=i^(P-2) 
		ans = ans * (a * quick_mod(b, p-2,p) % p) % p;  
	}*///注释掉这个,然后用以下方法求解提高速率 
	//直接用打表方式求解
	int num=id[p];//素数编号 
	ans=frc[num][n]*quick_mod(frc[num][n-m]*frc[num][m]%p,p-2,p)%p;
	return ans; 
}

LL Lucas(LL n,LL k,int cur)//求C(n,k)%mod[cur] 
{
	LL p=mod[cur];
	if(k==0)
		return 1%p;
	//return getC(n%p,k%p,cur)*Lucas(n/p,k/p,cur)%p;
	return getC(n % p, k % p,cur) * Lucas(n / p, k / p,cur) % p;  
}

/*void extend_Euclid(LL a,LL b,LL &x,LL &y)
{
	if(b==0)
	{
		x=1;
		y=0;
		return;
	}
	extend_Euclid(b,a%b,x,y);
	LL tmp=x;
	x=y;
	y=tmp-a/b*y;
}

LL CRT(LL a[],LL m[],int k)//求C(n,m)%M,其中M=(m0*m2*…*m(k-1)),mi为素数,则先用a[i]存储模方程C(n,m)%mi,
{                           //m[]存储所有素数因子mi,k表示总共有k个模方程,返回C(n,m)%M的值 
	LL M=1;
	LL ans=0;
	for(int i=0;i<k;i++)
		M*=mod[i];
	for(int i=0;i<k;i++)
	{
		LL x,y,tmp;
		LL Mi=M/m[i];
		extend_Euclid(Mi,m[i],x,y);
		if(x<0)
		{
			x=-x;
			tmp=mul(Mi,x,M);
			tmp=mul(tmp,a[i],M);
			tmp=-tmp;
		}
		else
		{
			tmp=mul(Mi,x,M);
			tmp=mul(tmp,a[i],M);
		}
		ans=(ans+tmp)%M;
	}
	while(ans<0)
		ans+=M;
	return ans;
}*/
int ans;
int main()
{
	//freopen("D:\\in.txt","r",stdin);
	int T,tempn,tempk,n,k,p;
	T=0;
	init();
	while(~scanf("%d%d%d",&n,&k,&p))
	{
		T++;
		if(k>n/2)
		{
			k=n-k;
		}
		mod[0]=p;
		ans=(Lucas(n+1,k,0)+n-k)%p;
		printf("Case #%d: %d\n",T,ans);
	 } 
  	return 0;
}





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林下的码路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值