Codeforces 711E - ZS and The Birthday Paradox

Published on 2016-08-30

题目地址

描述

某个星球有一年 2n(1n1018)2^n(1\le n\le {10}^{18}) 天,现在抽取 k(1k1018)k(1\le k\le {10}^{18}) 个人,问你至少有两个人生日为同一天的概率是多少?假设答案为 AB,AB\frac A B, A\perp B,你需要输出的是 Amod106+3Bmod106+3\frac {A\bmod {10}^6 + 3} {B \bmod {10}^6 + 3}

分析

如果 2n<k2^n<k,根据鸽笼原理,必定存在两个人的生日处于同一天,直接输出 1 1

下面假设 2nk2^n\ge k
直接算至少两个人生日相同的概率并不好算,我们算没有两个人生日相同的概率。首先固定第一个人的生日,则第二个人有 2n1{2^n - 1} 种选择,第三个人有 2n22^n - 2 种选择,可得没有两个人生日相同的概率:

所以至少有两个人生日相同的概率为:

P=1P¯=1(2n1)(2n2)(2n(k1))2(k1)nP = 1 - \bar P = 1 - \frac {(2^n - 1)(2^n - 2)\cdots(2^n - (k - 1))} {2^{(k - 1)n}}

假设 P¯=ab\bar P = \frac a b,则 P=babP = \frac {b - a} b,题目要求先化为最简形式(互质)再取模,根据 gcd(a,b)=gcd(ba,b)\gcd(a, b) = \gcd(b - a, b),我们只需要求出 ab\frac a b 的最简形式取模即可,这是因为:

P¯=a/gcd(a,b)b/gcd(a,b),P=(ba)/gcd(ba,a)b/gcd(ba,a)=1a/gcd(ba,a)b/gcd(ba,a)\bar P = \frac {a /\gcd(a, b)}{b/\gcd(a, b)}, P = \frac {(b - a) / \gcd(b - a, a)} {b /\gcd(b - a, a)} = 1 - \frac {a /\gcd(b - a, a)}{b/\gcd(b - a, a)}

由于 gcd(a,b)=gcd(ba,b)\gcd(a, b) = \gcd(b - a, b),所以我们可以先算 ab\frac a b 的最简形式取模,再用 1 来减,否则是不能够取模了之后再用 1 来减的。

我们现在求 P¯\bar P,观察发现 gcd\gcd 一定是 22 的幂,而且分母中的 22 明显比分子中的 22 要多,我们考虑求分子中有多少个 22,即能整除分子的最大的 2x2^x。我们的思路是求出分子分母的模,然后乘上 gcd\gcd 的逆元。

我们发现这样一个事实,如果有最大的 mm 使得 2m2^m 能整除 x2nx\le 2^n,那么它也是最大的 mm 使得 2m2^m 能整除 2nx2^n - x,反过来也成立。证明十分简单,对于 xx 来说,最大的 2m2^m 就是 lowbit(x)\mathrm{lowbit}(x),而使用 2n>x2^n > x 来减 xx 的话,必定有

lowbit(x)=lowbit(2nx)\mathrm{lowbit}(x) = \mathrm{lowbit}(2^n - x)

现在我们就是要求 (k1)!(k - 1)! 中有多少个 22 以得到 gcd\gcd,根据 Legendre's formula,可以在 O(logk)O(\log k) 的时间内求出,而使用 __builtin_popcountll() 的话更是可以除一个很大的常数。

接下来我们要算分子,如果 k1moduk - 1\ge \mathrm{modu},分子必然为 00,因为必然存在模的倍数。只剩下 k1<moduk - 1< \mathrm{modu} 的情况,这样分子就可以在至多 O(modu)O(\mathrm{modu}) 的时间内算出。

接着算分母 2(k1)n2^{(k - 1)n},发现 (k1)n(k - 1)n 会溢出,费马小定理来帮忙,只需要算 (k1)nmodmodu1(k - 1)n \bmod \mathrm{modu} - 1 的值就行了。

剩下的工作就是乘逆元,然后用 1 来减就行了。

P.S.:真是个数论综合知识运用的好题啊,我考试就是卡在转换到 (k1)!(k - 1)! 这一步了。

代码

//  Created by Sengxian on 08/30/16.
//  Copyright (c) 2016年 Sengxian. All rights reserved.
//  Codeforces 711E 数论
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const int modu = 1000003;
ll n, k;

inline ll mod_mul(ll a, ll b, int modu = ::modu) {
    ll res = 0;
    while (b) {
        if (b & 1) (res += a) %= modu;
        (a <<= 1) %= modu;
        b >>= 1;
    }
    return res;
}

inline ll mod_pow(ll a, ll b) {
    ll res = 1;
    while (b) {
        if (b & 1) (res *= a) %= modu;
        (a *= a) %= modu;
        b >>= 1;
    }
    return res;
}

int main() {
    scanf("%lld%lld", &n, &k);
    if (n <= 62 && k > (1LL << n)) return puts("1 1\n"), 0;
    ll num = 1, deno = 0, pow = 0, inv;
    // cal gcd (2 ^ pow)
    pow = k - 1 - __builtin_popcountll(k - 1);
    // cal numerator
    if ((k - 1) < modu) {
        ll tmp = (mod_pow(2, n) + modu - 1) % modu;
        for (int i = 0; i < k - 1; ++i) {
            (num *= tmp) %= modu;
            (tmp += modu - 1) %= modu;
        }
    } else num = 0;
    // cal denominator 2 ^ n(k - 1)
    deno = mod_pow(2, mod_mul(n, k - 1, modu - 1));
    // cal inv of gcd
    inv = mod_pow(mod_pow(2, pow), modu - 2);

    (num *= inv) %= modu, (deno *= inv) %= modu;
    printf("%lld %lld\n", (deno - num + modu) % modu, deno);
    return 0;
}