我找到了以下用于计算nCr的代码,但不理解它背后的逻辑。为什么这段代码有效?
long long combi(int n,int k)
{
long long ans=1;
k=k>n-k?n-k:k;
int j=1;
for(;j<=k;j++,n--)
{
if(n%j==0)
{
ans*=n/j;
}else
if(ans%j==0)
{
ans=ans/j*n;
}else
{
ans=(ans*n)/j;
}
}
return ans;
}
答案 0 :(得分:9)
这是一个聪明的代码!
一般来说,它的目的是计算以下公式:
ans = n! / (k!)(n-k)!
等于:
ans = n(n-1)(n-2) ... (n-k)...1 / k(k-1)...1 * (n-k)(n-k-1) ... 1
明显取消后:
ans = n(n-1)(n-2)..(n-k+1) / k!
现在请注意,提名者和分母具有相同数量的元素(k元素)
所以ans的计算方式如下:
ans = 1 // initially
ans *= n/1
ans *= (n-1)/2
ans *= (n-2)/3
.
.
.
ans *= (n-k+1)/k
再看一下代码,你会注意到:
ans
在每次迭代时乘以n
n
在每次迭代(n--
)ans
在每次迭代时除以j
这正是发布代码所做的事情。现在让我们看一下循环中不同条件的含义,分母从n
开始,分母从1到k
,所以变量j
被分配给分母吧?
1)if(n%j==0)
n/j
是(可计算的),则在每一步所以我们首先在这里计算它而不是乘以整个ans
,这种做法将结果保持在最小的可能值。
2)else if(ans%j==0)
如果我们无法计算n/j
,但实际上可以计算ans/j
,那么每一步都是如此,这样可以说:
ans /= j; //first we divide
ans *= n; //then we multiply
这总是让我们的整体产量尽可能小,对吗?
3)last condition
在每一步,如果在这种情况下我们既不能计算n/j
也不能计算ans/j
,我们就没有幸运地先划分再乘以(因此保持结果很小)。但是我们需要继续进行 - 尽管我们只剩下一个选择
ans *= n; // multiply first
ans /= j; // then divide
ET VOILA!
示例强>
考虑案例3C7
我们知道答案是7!/ 3!* 4!
因此:ans = 7*6*5 / 1*2*3
让我们看看每次迭代会发生什么:
//1
ans = 1
//2
n = 7
j = 1
ans = ans * n/j
first compute 7/1 = 7
then multiply to ans
ans = 1*7
ans = 7
//3
n = 6
j = 2
ans = ans* n/j
evaluate n/j = 6/2 (can be divided)
n/j = 3
ans = ans *(n/j)
= 7 * 3
= 21
// 4
n = 5
j = 3
ans = ans * n/j
evaluate n/j = 5/3 oppsss!! (first if)
evaluate ans/j = 21/3 = 7 YES (second if)
ans = (ans/j)*n
= 7*5
= 35
// end iterations
请注意,在最后一次迭代中,如果我们直接计算,我们会说:
ans = ans*n/j
= 21 * 5 / 3
= 105 / 3
= 34
是的,它确实找到了正确的结果,但同时价值飞到105之后再回到35.现在想象一下计算真实的大数??
<强>结论强>
此代码仔细计算二项式系数,试图在每个计算步骤中保持输出尽可能小,它通过检查是否可以除(然后执行)来执行,因此它能够计算一些非常大的int
直接编码无法处理(可能发生OverFlow)
答案 1 :(得分:4)
要回答部分问题,请考虑n choose k
的条目构成Pascal's triangle这一事实。由于Pascal的三角形是对称的,将参数k
移动到左半部分就足够了,这是用
k=k>n-k?n-k:k;
语句;看看C的条件运算符的定义。
此外,结果ans
在开头初始化为包含1
,这是Pascal三角形中每一行的第一个条目,这意味着最初ans
实际上是n choose j
{{1}}。
答案 2 :(得分:1)
事实是1&lt; = k&lt; = n / 2的nCr与n / 2 + 1&lt; = k&lt; = n中的相同。因此,k的第一个变化使其值为左半部分的值。还有一件事nCk意味着(n *(n-1) ..... (nk))/(k *(k-1)* .... * 2 * 1)所以上面的代码迭代地应用它。
答案 3 :(得分:-2)
是肯定的。 [N选择K]减少了它的阶乘,因为除数和除数共享许多因子相互抵消x / x = 1(对于x> 0) 诀窍是不计算大因子,因为这些大因子需要太多的地址空间(太多位)
第一个技巧是在分割之前减少分数。 第二个技巧是在条件内做模数,为当前迭代选择3个操作之一。这可以不同的方式完成,并且整数模数被选择为快速运算符,跳过一些较慢的整数除法方法。
你迭代地遍历帕斯卡三角形。 你走的每一条路都会成倍增加。
每个迭代步骤有3种可能的分支路径: 3个步骤中的每一个将累加器“ans”与不同的值相乘,表示帕斯卡三角形上2个“位置”之间的因子。 你最终总是进行N次乘法,其中N是迭代次数,最后是二项式系数的值。
N是你想知道的帕斯卡三角形的列#,你累加一个N,乘以某个东西,同时每次迭代减少帕斯卡三角形的列数(和行数)N = N-1
J = 1;
ANS = 0;
//在每次迭代中;
ans=ans*n;
n=n-1;
ans=ans/j;
j=n+1;
整数除法很慢,可以跳过(或者通过使除数变小来加快)至少一次,通常多次(因为在帕斯卡三角形中有很多共享的素因子),这是完成的通过模数条件。
帕斯卡三角形是非常对称的(在总结其域时),因此这是有效的。
帕斯卡三角形的(部分)列之和之间的差异显示了对称性,这对于乘法和除法非常重要。
只是观看一些关于帕斯卡三角形的对称性和身份的YouTube视频。