무작위 변수의 중복 확률 계산

문제 설명

\(m\) 개의 \([0,2^n)\) 범위 내에서 균일하게 무작위로 선택된 정수 변수가 있을 때, 최소 두 개의 변수 값이 동일할 확률을 구하는 문제입니다.

정확한 계산을 위해 결과를 \(a/b\) 형태로 표현하며, 여기서 \((a,b)=1\). 이때, \(a\)와 \(b\)는 \(10^6+3\)에 대해 모듈러 연산 후 출력해야 합니다.

데이터의 70%에 대해, \(m \le 10^6\) 모든 데이터에 대해, \(1 \le 10^{18}\), \(2 \le m \le 10^{18}\)

【예시 1 입력】

3 2

【예시 1 출력】

1 8

【예시 2 입력】

1 3

【예시 2 출력】

1 1

【예시 3 입력】

4 3

【예시 3 출력】

23 128

해결 전략

먼저, 모든 값이 다른 경우의 확률을 계산한 후 이를 이용하여 적어도 두 값이 같은 경우의 확률을 구합니다. 즉,

[1 - \frac{2^n \times (2^n -1)\times ... \times (2^n -m +1)}{2^{nm}} ]

이 식을 간단히 하기 위해, 먼저 \(m \ge mod\)일 때 분자에 \(mod\)로 나누었을 때 0이 되는 항이 반드시 존재함을 알 수 있습니다. 그러나 답을 바로 1로 출력할 수 없으므로, 분자와 분모의 약분 방법을 고려해야 합니다.

분자의 각 항은 모두 2의 거듭제곱에서 어떤 수를 뺀 값으로 구성되며, 이들 사이의 관계를 이용하여 2의 지수를 계산할 수 있습니다. 예를 들어, \(8 - 2 = (1000)_2 - (0010)_2 = (0110)_2\).

이 관찰을 바탕으로, \(2^n \times (2^n -1)\times ... \times (2^n -m +1)\)에서 2의 지수 개수를 구하는 것은 사실상 \((m-1)!\)에서 2의 지수 개수를 구하는 것과 같습니다.

이를 효율적으로 계산하기 위한 방법으로는 Legendre's Theorem을 사용할 수 있습니다:

[\text{CountOf2}(n!) = \left\lfloor \frac{n}{2} \right\rfloor + \left\lfloor \frac{n}{4} \right\rfloor + \left\lfloor \frac{n}{8} \right\rfloor + \cdots ]

이제 분자를 어떻게 계산할지 생각해봅시다. \(O(m)\) 시간 복잡도로 직접 계산하지 않고, \(m \le mod\)인 경우만 처리하면 됩니다.

분모를 약분하려면 지수에서 빼주면 되고, 분자는 Fermat's Little Theorem을 통해 나눗셈을 곱셈으로 변환할 수 있습니다:

[a^{-1} \equiv a^{p-2} \pmod{p} ]

Code

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MOD = 1e6 + 3;

ll powerMod(ll base, ll exp, ll mod) {
    ll result = 1;
    while (exp > 0) {
        if (exp & 1) result = (result * base) % mod;
        base = (base * base) % mod;
        exp >>= 1;
    }
    return result;
}

ll countTwoFactors(ll x) {
    return x ? x / 2 + countTwoFactors(x / 2) : 0;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(NULL);

    ll n, m;
    cin >> n >> m;
    
    ll twoFactors = countTwoFactors(m - 1) + n;
    ll invDenom = powerMod(powerMod(2, twoFactors, MOD), MOD - 2, MOD);
    ll denom = powerMod(powerMod(2, n, MOD), m, MOD) * invDenom % MOD;

    if (m >= MOD) {
        cout << denom << " " << denom;
        return 0;
    }

    ll current = (powerMod(2, n, MOD) - (m % MOD) + 1 + MOD) % MOD;
    for (ll i = 1; i < m; ++i) {
        current = (current * (powerMod(2, n, MOD) - i + 1)) % MOD;
    }

    current = (current * invDenom) % MOD;
    cout << (denom - current + MOD) % MOD << " " << denom;

    return 0;
}

태그: Legendre's Theorem Fermat's Little Theorem Modular Arithmetic C++

5월 30일 07:47에 게시됨