首页 > 其他分享 >关于求阶乘逆元

关于求阶乘逆元

时间:2024-04-13 19:34:38浏览次数:9  
标签:int 逆元 maxn 关于 阶乘 预处理 mod

以下思路背景均来自题目牡牛和牝牛


  • 预处理做法

众所周知:

\[C_n^m=\frac{n!}{m!(n-m)!} \]

众所周知知:

c++中除法很不稳定,所以需要逆元。

所以真正在运用时,我们的函数会这么写:

int C(int a,int b)
{
	if(a<b)
		return 0;
	return jc[a]*ny[b]%mod*ny[a-b]%mod;
}

啊哦忘说要取模了

这样写看着十分简便,不过需要预处理出\(jc\)(阶乘)与\(ny\)(阶乘逆元)的值。

而这个范围往往是很大的,一般情况下处理范围为\(0\)$mod-1$,但在范围$0$\(maxn\)之间也是可以实现的。

所以,范围右边界取值,应为\(min(mod,maxn)\),大多数情况有\(mod>maxn\),但是也有太大导致开不了数组的情况,这就得下面优化了。

故有:

	jc[0]=ny[0]=1;
	for(int i=1;i<=mod-1;i++)
		jc[i]=jc[i-1]*i%mod;
	ny[mod-1]=iv(jc[mod-1]);
	for(int i=mod-2;i>=1;i--)
		ny[i]=ny[i+1]*(i+1)%mod;

而这之中又出现了一个新的函数取逆元,它需要用到\(exgcd\),故又有:

void exgcd(int a,int b,int &x,int &y)
{
	if(b==0)
	{
		x=1,y=0;
		return;
	}
	int x0,y0;
	exgcd(b,a%b,x0,y0);
	x=y0;
	y=x0-(a/b)*y0;
}
int iv(int a)
{
	int x,y;
	exgcd(a,mod,x,y);
	return (x%mod+mod)%mod;
}

这种写法的好处在于预处理已经做完了几乎全部工作,整个程序的复杂度集中在这里,之后再进行大量运用\(C\)函数时会非常节省时间。

但问题也很明显:当\(mod\)的值很大时,时间复杂度会大幅上升。

例如

image

  • 预处理优化

一个小优化:可以只需预处理阶乘。

预处理为:

	jc[0]=1;
	for(int i=1;i<=maxn;i++)
		jc[i]=jc[i-1]*i%mod;

当然,这里范围取\(n\)还是\(mod-1\)是个玄学问题,请大佬指教%%%

组合函数为:

int C(int n,int m){
	if(n<m)
		return 0;
	return jc[n]*qpow(jc[m]*jc[n-m]%mod,mod-2)%mod; 
}
少预处理一个数组的时间优化还是挺多的。

image

这道题\(maxn\)和\(mod\)的时间差距还是挺大的,上面是\(maxn\)。

  • 优化做法

优化后时间已经很短了,但有一种情况,它还是不能实现:

\(998244353\),\(1000000007\),都是比较常见的取模数,显然,数组是无法容纳它们的。

这时候,就有了另一种处理方式:优化掉预处理,在每次应用函数时算出每个阶乘。

它的特点也很明显:在处理单次或少次询问时有着更优的时间复杂度;也因此,它在询问次数较多时表现就很不尽人意了。

实现方法:

与上方优化后一样,用快速幂求逆元,只需要一个快速幂函数,而且不需要预处理,只是组合函数内东西稍微多了点。

int C(int a,int b)
{
	int aa=1,bb=1;
	fo(i,a-b+1,a)//这是化简后的阶乘结果
		aa=aa*i%mod;
	fo(i,1,b)
		bb=bb*i%mod;
	return aa*qpow(bb,mod-2)%mod;
}
  • 总结

上面三种做法都很常用,主要依据于大家自己的习惯。

做题时应先根据题意做出判断,如果预处理的范围超过了数组能承受的大小,则只能选用(大概)第三种方法;若询问次数较多,则前两种方法更优。

所以:寸有所长 尺有所短还是都记下来最保险。

完结撒花

image

标签:int,逆元,maxn,关于,阶乘,预处理,mod
From: https://www.cnblogs.com/Ratio-Yinyue1007/p/18133149

相关文章

  • 关于PHP编码的选用
    半知半觉地到了老而不肖的年纪,过往点滴,是有那么十数件要么心亏,要么愤慨,要么算了,要么绮望...诸等反复嚼陈的,选专业就是其一,写码刮不出水链会叹基础差,好多概念糊涂不清,要是有在计算机系挨过,是否捉窘就能少一些呢。说起文件编码,打从业就没怎么关注过,乱码吗?用记事本打开二进制文件就......
  • 关于链接脚本和汇编导致的数据段初始化错误的问题
    第一个链接脚本存在data段初始化失败的问题,第二个link脚本增加了At>flash就可以正常的运行了,是为什么?如果只是链接错误的话,那么汇编从ram向同地址的ram中搬运为什么就会运行出错?链接脚本分别如下:有错误的类型MEMORY{flash(rxai!w):ORIGIN=0x20000000,LENGTH......
  • 关于Nodejs入坑!!!
    关于Nodejs入门什么是nodejs?一个开源与跨平台的JavaScript运行时环境;可以理解为Node.js就是一个服务器端的、非阻塞式I/O的、事件驱动的JavaScript运行环境Node作为一个新兴的前端框架,后台语言,有很多吸引人的地方:RESTfulAPI,单线程。https://www.ruanyifeng.com特点:1......
  • 关于双$$变量覆写;
    本质上而言双$原本功能是允许用户自定义变量;但未过滤输入,导致传入参数是一代码中已经定义好的变量,或者全局变量时导致数据篡改,或调用危险函数;总之;导致的变量覆盖其实就是允许可控参数作为一个变量,这个变量可以是自定义变量,也可以是代码中的变量或全局变量或特殊构造的函数"......
  • 关于UNNAMED00004
    使用闪回恢复删除表空间。以下为截取的alert日志FriApr1216:28:512024droptablespaceusersincludingcontentsanddatafilesFriApr1216:28:572024DeletedOraclemanagedfile+DATA/ORCL/DATAFILE/users.280.1166105459Completed:droptablespaceusersin......
  • 关于C++作用域符的一种用法
    当作用域符号::前不带类名,或者namespace名的时候,表示是全局作用域的意思,也就是表示所调用的函数是全局函数,或者是某个动态库的函数,这对与代码的可阅读性有很大的帮助,因为它与类型成员函数的调用做了区分,表明该函数不是类成员函数如下图的send()函数,其前面的::表明send()函数不是......
  • 关于委托的新认识
    缘由在技能系统的业务开发中,需要用到一个字符串对应一个方法,我首先想到的就是事件,但我想尝试一下别的后悔了,平白无故给自己玩坑数据结构:Dictionary<string,Action>那如何去初始化它呢问了ChatGPT,最终的方案是使用一个SkillManager,其中的每一个方法,就是一个技能,那现在就得到......
  • 关于工业AI辅助制造(模具设计、模样生产制造环节)
    关于工业AI辅助制造(模具设计、模样生产制造环节)AI技术的具体使用场景:AI辅助模具设计;AI辅助模具安装工艺参数调整。具体方案设想:AI辅助模具设计:使用AI大模型对历史已有的设计方案数据和设计目标数据进行学习,对设计目标和设计方案进行关联并实现在新设计要求的情......
  • Oracle关于半连接SQL执行计划的执行路径一些有趣的实验
     Oracle关于半连接SQL执行计划的执行路径一些有趣的实验 从摩天轮的问答里边看到的一个问题,https://www.modb.pro/issue/34573大概有这么条SQL(包括环境构造语句),如何强制走出nestedloops的执行路径。createtabletb1asselect*fromdba_objects;createtabletb2as......
  • 关于期望 dp 的一点思考
    一、前言只是一些自己的理解,并不知正确与否。首先期望\(dp\)分为伪期望\(dp\)和真期望\(dp.\)二、伪期望\(dp\)对于伪期望\(dp\)来说,其在定义状态之后,一般可以认为状态之间的转移是线性的,即每一个\(dp\)状态转移到何处具有唯一对应性,只不过具体的实现上经过了概......