Numbers of combinations modulo m, efficiently.

huangapple go评论62阅读模式
英文:

Numbers of combinations modulo m, efficiently

问题

首先,我现在解决的是一个编程问题,而不是数学问题。

问题是:

Anish拿到了一个不偏的硬币,他抛了n次硬币,然后他要求Gourabh计算所有可能出现j个正面的情况的数量,其中j从0到n。由于可能的结果数量可能很大,他会告诉你对m取模后的值。明确地说,我们需要为每个j的值返回一个整数。

问题很简单,但问题是时间限制,为1.5秒,但输入n可以很大,高达200000。

我使用了math.comb来计算这些值,但运行时间超过了1.5秒。

那么,有没有更快速地计算组合的方法?

编辑#1:

示例输入:
2 998244353

示例输出:1 2 1

编辑#2:

这是我尝试过的代码:

import math
 
n,m=input().split()
n = int(n)
m = int(m)
 
l = []
for i in range(n+1):
    l.append(math.comb(n,i)%m)
print(*l)

请告诉我如果这个问题不适合在这个网站上提问,并建议一个适合发布此问题的适当SE网站。先行致谢!此问题来自2个月前结束的一个学院间竞赛。

这是原始问题链接: https://codeforces.com/gym/430360/problem/B (你需要一个账户,并首次点击这里 here 输入比赛链接)。

如果你无法查看该问题,请在下面找到图片。

Numbers of combinations modulo m, efficiently.

英文:

First of all I'm solving a programming problem rather than a math problem now.

The question is

> Anish got an unbiased coin and he tossed it n times and he asked Gourabh to count all the number of possible outcomes with j heads, for all j from 0 to n. Since the number of possible outcome can be huge, he will tell the values modulo m. To be clear, we need to return one integer per value of j.

The question is simple, but the problem arises with the time limit, being 1.5 seconds, but with input n as large as 200000.

I used math.comb to calculate the values, but it took more than 1.5 seconds to run.

So, are there any ways to calculate combinations in a faster way?

Edit#1:

>Sample input:
2 998244353

>Sample output: 1 2 1

Edit#2:

Here is the code that I've tried:

import math
 
n,m=input().split()
n = int(n)
m = int(m)
 
l = []
for i in range(n+1):
    l.append(math.comb(n,i)%m)
print(*l)

P.S: Please let me know if this is off topic for this site and suggest a suitable SE site to post this question. Thanks in advance! This question is from an inter college contest which ended 2 months ago.

Here is the original problem: https://codeforces.com/gym/430360/problem/B (you'll need an account, and first time follow the "Contest Link" here to enter).

In case you are not able to view the problem, please find the picture below.
Numbers of combinations modulo m, efficiently.

答案1

得分: 8

Here is the translated content:

由于您需要输出所有 j 的值,您应该使用以下生成函数:

如果 j >= 1,那么 C(n,j) = C(n,j-1) * (n+1-j) / j

通常,在这种类型的问题被提出时,模数 m 是大于 n 的素数。这使得在模 m 下进行所有这些计算相对容易,因为每个 j 都有一个乘法逆元。

事实上,用非素数模数提出这个问题是如此不寻常,以至于我敢打赌 Codeforces 的问题描述只是忘了提到它。我建议您使用素数模数的假设来尝试解决它。

如果您使用的是 Python 3.8 或更高版本,则语言中内置了模逆元,您可以像这样使用它:

def getBinomialCoefficientsMod(n,m):
    result = [1]
    for j in range(1,n+1):
        result.append(( result[j-1] * (n+1-j) * pow(j,-1,m) )%m)
    return result

编辑:好吧,事实证明 m 不总是足够大的素数,我不想让这个答案不完整,所以这里有一个适用于复合数或小 m 的版本:

def getBinomialCoefficientsMod(n,m):

    # 获取模数的质因数
    facs=[]
    fac=2
    mleft = m
    while fac*fac<=mleft:
        if m%fac==0:
            facs.append(fac)
            while mleft%fac==0:
                mleft//=fac
        fac+=1
    if mleft>1:
        facs.append(mleft)

    result = [1]
    # 上一次结果相对于 m 是互质的因子
    rpresult = 1
    # 上一次结果中模数的质因数的幂
    exponents = [0]*len(facs)

    for j in range(1,n+1):
        p=1
        num = n+1-j
        den = j

        # 从 num 和 den 中去除模数的因子,跟踪它们的幂,并获取它们的乘积
        for i in range(len(facs)):
            fac = facs[i]
            while num%fac==0:
                exponents[i]+=1
                num//=fac
            while den%fac==0:
                exponents[i]-=1
                den//=fac
            p = p*pow(fac,exponents[i],m)
    
        rpresult = (rpresult * num * pow(den,-1,m)) % m
    
        result.append(( rpresult * p )%m)

    return result

英文:

Since you need to output values for all j, you should use this generating function:

If j &gt;= 1, then C(n,j) = C(n,j-1) * (n+1-j) / j

Normally, when this sort of question is asked, the modulus m is a prime larger than n. This makes it relatively easy to do all these calculations mod m, because every j will have a multiplicative inverse.

In fact, it is so unusual to ask this question with a non-prime modulus, that I bet the codeforces problem description just fails to mention it. I would try it with the prime modulus assumption.

If you're using python 3.8 or better, then there is a modular inverse built into the language, and you can do it just like this:

def getBinomialCoefficientsMod(n,m):
    result = [1]
    for j in range(1,n+1):
        result.append(( result[j-1] * (n+1-j) * pow(j,-1,m) )%m)
    return result

Edit: Well, it turns out that m is not always a large enough prime, and I don't want to leave this answer incomplete, so here's a version that works with composite or small m:

def getBinomialCoefficientsMod(n,m):

    # get the prime factors of the modulus
    facs=[]
    fac=2
    mleft = m
    while fac*fac&lt;=mleft:
        if m%fac==0:
            facs.append(fac)
            while mleft%fac==0:
                mleft//=fac
        fac+=1
    if mleft&gt;1:
        facs.append(mleft)

    result = [1]
    # factor of the last result that is relatively prime to m
    rpresult = 1
    # powers of the prime factors of m in the last result
    exponents = [0]*len(facs)

    for j in range(1,n+1):
        p=1
        num = n+1-j
        den = j

        # remove factors of the modulus from num and den,
        # track their exponents, and get their product
        for i in range(len(facs)):
            fac = facs[i]
            while num%fac==0:
                exponents[i]+=1
                num//=fac
            while den%fac==0:
                exponents[i]-=1
                den//=fac
            p = p*pow(fac,exponents[i],m)
    
        rpresult = (rpresult * num * pow(den,-1,m)) % m
    
        result.append(( rpresult * p )%m)

    return result

答案2

得分: 8

使用通常的乘法公式来计算下一个数,但保持数字较小。首先看一个为清晰起见的简单版本。

Naive

def naive(n, m):
    c = 1
    yield c
    for k in range(n):
        c = c * (n-k) // (k+1)
        yield c % m

n, m = map(int, input().split())
print(*naive(n, m))

需要大约30秒,因为c变得非常大,最多有60204位(199991比特)。使用这么大的数字进行计算很慢。

Fast

与其朴素地计算这些大的c并仅在输出时使用模m,不如在整个过程中保持c小于模m。在网站上被接受,耗时约0.68秒。

from math import gcd

def fast(n, m):
    c = 1
    G = 1
    yield c
    for k in range(n):

        mul = n - k
        while (g := gcd(mul, m)) > 1:
            mul //= g
            G *= g

        div = k + 1
        while (g := gcd(div, m)) > 1:
            div //= g
            G //= g

        c = c * mul * pow(div, -1, m) % m
        yield c * G % m

n, m = map(int, input().split())
print(*fast(n, m))

尝试在线运行!

在模数下进行乘法是可行的。如果只有c = c * (n-k),我们可以直接执行c = c * (n-k) % m

除法不允许这样做。因此,我们不是通过k+1除,而是通过它的倒数(k+1)-1在模m下进行乘法。某个数字x的倒数是数字x-1,这样你就会得到x·x-1 = 1。例如,10模7的倒数是3。因为将7和3相乘得到21,它对10取模等于1。

下一个问题:并非所有数字在模m下都有倒数。例如,6在模10下没有倒数。你不能将6与任何整数相乘得到1(模10)。因为6和10有公共除数2。我们将尽可能多地提取与m中公共因数的乘法器/除数的质因数到一个单独的数字G中。然后在模m下用剩下的部分更新c。然后将c和G合并以进行输出。

我对n=200000,m=998244353(问题中的大素数)得到的粗略时间如下:

naive: 30.0秒
fast:   1.0秒
Matt's: 1.0秒

对于n=200000,m=23571113171923:

naive: 30.0秒
fast:   1.2秒
Matt's: 4.8秒

我认为最坏的情况是一个具有许多质数的模数,例如m=23571113171923,它最大化了我的G。对于n=200000,G增长到127位是没问题的。

我的解决方案/解释是对Leetcode上类似问题的解决方案。那个问题的模数是10,我硬编码了因子2和5,并计数它们,而不是像我在这里做的那样将它们乘到一个数字G中。也许我会用这个通用解决方案重新访问它...

英文:

Using the usual multiplicative formula to compute the next number from the previous, but with keeping the numbers small. Let's first look at a naive version for clarity.

Naive

def naive(n, m):
    c = 1
    yield c
    for k in range(n):
        c = c * (n-k) // (k+1)
        yield c % m

n, m = map(int, input().split())
print(*naive(n, m))

Takes me ~30 seconds with n=200000. Because c grows very large, up to 60204 digits (199991 bits). And calculations with such large numbers are slow.

Fast

Instead of naively computing those large c and using modulo m only for output, let's keep c small throughout, modulo m. Got accepted on the site, taking ~0.68 seconds.

from math import gcd

def fast(n, m):
    c = 1
    G = 1
    yield c
    for k in range(n):

        mul = n - k
        while (g := gcd(mul, m)) &gt; 1:
            mul //= g
            G *= g

        div = k + 1
        while (g := gcd(div, m)) &gt; 1:
            div //= g
            G //= g

        c = c * mul * pow(div, -1, m) % m
        yield c * G % m

n, m = map(int, input().split())
print(*fast(n, m))

Attempt This Online!

Multiplication is fine under modulo. If it were only c = c * (n-k), we could just do c = c * (n-k) % m.

Division doesn't allow that. So instead of dividing by k+1, we multiply with its inverse (k+1)<sup>-1</sup> modulo m. The inverse of some number x is the number x<sup>-1</sup> so you get x&middot;x<sup>-1</sup> = 1. For example, 7<sup>-1</sup> modulo 10 is 3. Because multiplying 7 and 3 gives you 21, which is 1 (modulo 10).

Next issue: Not all numbers have an inverse modulo m. For example, 6 doesn't have an inverse modulo 10. You can't multiply 6 with any integer and get 1 (modulo 10). Because 6 and 10 have common divisor 2. What we'll do is invert as much of 6 as possible. Extract the common divisor 2, leaving us with 3. That does have an inverse modulo 10 (namely 7).

So extract prime factors in the multipliers/divisors common with m into a separate number G. And update c with what remains, modulo m. Then combine c and G for output.

Rough times I get for n=200000, m=998244353 (the large prime from the question):

naive: 30.0 seconds
fast:   1.0 seconds
Matt&#39;s: 1.0 seconds

For n=200000, m=2*3*5*7*11*13*17*19*23:

naive: 30.0 seconds
fast:   1.2 seconds
Matt&#39;s: 4.8 seconds

I think worst case is a modulus with many primes like m=2*3*5*7*11*13*17*19*23, that maximizes my G. With n=200000, G grows up to 127 bits. Nothing to worry about.

My solution/explanation for a similar problem on Leetcode. That had modulus 10 and I hardcoded factors 2 and 5 and counted them instead of multiplying them into a number G like I did here. Maybe I'll revisit it with this general solution...

答案3

得分: 2

我建议使用类似于模乘法的方法。

如果你有两个数字ab,并且你计算它们的乘积对模取余,你可以先对每个数字取模,然后再计算乘积。即:

(a * b) % mod = ((a % mod) * (b % mod)) % mod

考虑到:C(n, r) = n! / (r! * (n - r)!)
通过在a中传递mod,你可能可以获得一些很好的计算减少。

英文:

I will sugest to use something like Modulo Multiplication.

If you have two numbers a and b, and you calculate their product modulo mod, you can take the modulo of each number first and then calculate the product. That is:

(a * b) % mod = ((a % mod) * (b % mod)) % mod

Taking into account that: C(n, r) = n! / (r! * (n - r)!)
You can probably get some nice reduction of computation passing mod inside a getting a general result.

huangapple
  • 本文由 发表于 2023年5月29日 21:35:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/76357846.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定