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

本文介绍了一道经典的算法题目,通过使用杨辉三角的特性,结合组合数学的知识,实现了一个高效的算法来解决从杨辉三角顶部到底部某点的最小路径和问题。


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;
}





第一阶段:搜索算法核心突破(3.25-3.28 | 4天) 3.25-3.26:DFS基础与剪枝 学习内容:回溯模板、排列/子集生成、剪枝技巧(可行性/最优性剪枝) 真题练习: 全排列问题(第七届《凑算式》变种) 迷宫路径计数(二维矩阵搜索) 3.27-3.28:BFS与连通性问题 学习内容:队列实现BFS、层序遍历、连通块计数 真题练习: 第七届《剪邮票》(DFS验证5格连通性) 岛屿数量问题(连通块计数) 第二阶段:动态规划专题(3.29-4.1 | 4天) 3.29-3.30:线性DP与递推 学习内容:爬楼梯模型、打家劫舍变种、递推公式设计 真题练习: 第七届《煤球数目》(直接递推) 第十四届《接龙数列》(字符串状态转移)3.31-4.1:背包DP与字符串DP 学习内容:01背包模板、滚动数组优化、最长公共子序列 真题练习: 第十二届《砝码称重》(01背包变种) 编辑距离问题(字符串DP) 第三阶段:数论+贪心强化(4.2-4.4 | 3天) 4.2:质数与GCD 学习内容:埃氏筛法、欧几里得算法、因数分解 真题练习:第十二届《货物摆放》(求因数组合) 4.3:快速幂与模运算 学习内容:快速幂模板、逆元计算(选学) 真题练习:大数模问题(如计算10^{18} \mod 710 18 mod7) 4.4:贪心策略 学习内容:区间调度、相邻交换策略 真题练习:第四届《翻硬币》(贪心翻转)、第九届《乘积最大》 第四阶段:数据结构+图论(4.5-4.7 | 3天)4.5:并查集与优先队列 学习内容:路径压缩、按秩合并、Dijkstra堆优化 真题练习:第十二届《城邦》(并查集预处理) 4.6:栈与图论基础 学习内容:表达式计算、Dijkstra最短路径 真题练习:第十二届《路径》(Dijkstra模板题) 4.7:拓扑排序与最小生成树 学习内容:Kahn算法、Kruskal实现 真题练习:第十四届《飞机降落》(拓扑排序思想)第五阶段:二分+综合复习(4.8-4.10 | 3天) 4.8:二分查找与答案 学习内容:边界处理、最大值最小化问题 真题练习:第十二届《直线》(排序去重+二分优化) 4.9-4.10:全真模拟与查漏补缺 任务:限时刷近3年真题(重点做搜索、DP数论题) 错题复盘:整理易错代码片段(如DFS状态遗漏、DP初始化错误)时间完全不够 我3.25-3.29都没把DFS要学习的内容学完也还没加以联系,这份安排太紧凑了,难以让我真的深入理解这些算法,只能明白个模板,帮我再做一份学习计划吧,可以删减些比赛出现可能性相对较低的算法或者算法中的学习内容,以求留下广东省十六届以前蓝桥杯c赛道b组出现频率最高能覆盖尽量多考试类型的算法,帮我再精简筛选一下,然后按照在2025年广东省蓝桥杯c赛道b组可能出现的频率的顺序帮我重新安排一下学习内容,以助我拿下奖项。从3.30开始给我从新安排一下,现在学了DFS的迷宫,全排列,回溯模板,但还没加以真题练习
最新发布
03-30
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

林下的码路

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

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

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

打赏作者

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

抵扣说明:

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

余额充值