C. 简单的玄学
题目描述
有 \(m\) 个在 \([0,2^n)\) 内均匀随机取值的整型变量,求至少有两个变量取值相同的概率.
为了避免精度误差,假设你的答案可以表示成 \(a/b\) 的形式(其中\((a,b) = 1\) ),你需要输出 \(a\) 和 \(b\) 对 \(10^6+3\) 取模后的值.
对于 \(70\%\) 的数据, \(m \le 10^6\)
对于 \(100\%\) 的数据 ,\(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
基本思路
首先考虑正难则反,与其计算重合概率不如计算没有一个重合的概率,于是有:
接下来先思考如何约分,首先我们先观察到一个特殊性质。当 \(m \ge mod\) 时,分子上一定存在一项对 \(mod\) 取模后为零的项。但由于我们是先对后面的分子约分,再计算答案,所以我们还不能直接抛出\(1\ 1\)。
然后我们真正来思考如何给分子和分母约分,自然我们只能在分子累乘的那几项中动手脚。
我们很容易观察到分子都是二的次幂减去一个数,在二进制下我们十分容易观察到一个性质,对于任意 \(2^n -i = j\),都有 \(j\) 的二的约数个数(即指数),等于 \(i\) 的二的约数个数。
举几个例子,\(8 - 2\) 有\((1000)_2 - (0010)_2 = (0110)_2\) 。
而我的同学 zsk(luogu @zk_zk) 给出了更为可靠的证明:
我们设 \(i = q * 2^{k1}\) , \(j = p * 2^{k2}\)
其中\(p\) 和 \(q\) 均没有 \(2\) 因子.
则 \(2^n = q * 2^{k1} + p * 2^{k2}\)
我们不妨设 \(k1 \le k2\), \(k2 - k1 =x\)
故有 \(2^n = 2^{k1} * (p + q * 2^x)\)
而其中 \(2^{k1} \le 2^n\) ,\(p , q\) 均为奇数,故 \(2^x\) 必定为 \(1\) ,故 \(k1 = k2\)
证毕.
那么我们求 \(2^n * (2^n -1)* (...) * (2^n -m +1)\) 中 \(2\) 因子个数(指数),相当于求 \((m-1)!\) 中 \(2\) 约数个数。
我们查询资料可以找到一个 \(O(log m)\) 的求解方法,即勒让德定理(Legendre's Theorem):
对于正整数\(n\)和质数\(p\),\(n!\)中包含的\(p\)的幂次为:
当\(p=2\)时,计算\(n!\)中2的因子个数:
\(\text{CountOf2}(n!)\)满足以下递推关系:
然后怎么算出分子先呢?总不能用 \(O(m)\) 的解法跑一遍累乘分子吧……还真可以,我们上面不是给出说明了吗,我们只需要处理 \(m \le mod\) 时的情况,不超过 \(10^6\)
那么分母约分直接在指数上减去就可以了,那分子如何执行除法呢?这就不得不提到费马小定理了:
若\(p\)是一个质数,且\(a\)是一个与\(p\)互质的整数,则:
我们稍微变形一下:
即在模 \(p\) 意义下 我们除去 \(a\) 等价于乘上 \(a^{p-2}\)。
什么?你还怕指数储存爆掉 \(long long\)?,你这样写不就行了:
ll rtmp=qpow(qpow(2,tmp,mod),(mod-2),mod);
Code
#include<bits/stdc++.h>
using std::cin;
using std::cout;
typedef long long ll;
const ll mod=1e6+3;
ll qpow(ll a,ll b,ll c){
ll ret=1;
while(b){
if(b&1) ret=ret*a%c;
a=a*a%c;
b>>=1;
}
return ret;
}
ll f(ll x){
return x?f(x/2)+x/2:0;
}
long long n,m;
ll tmp,cnt,cur,cel;
int main(){
std::ios::sync_with_stdio(false);
cin>>n>>m;
tmp=f(m-1)+n;//2的因子
ll rtmp=qpow(qpow(2,tmp,mod),(mod-2),mod);
cnt=qpow(qpow(2,n,mod),m,mod)*rtmp%mod;//分母
if(m>=mod){
cout<<(long long)cnt<<" "<<(long long)cnt;
return 0;
}
cel=qpow(2,n,mod);
cur=(qpow(2,n,mod)-(m%mod)+1+mod)%mod;
for(ll i=1;i<m;i++){
cur*=(cel-i+1);
cur%=mod;
}
cur=(cur*rtmp)%mod;
cout<<(long long)(cnt-cur+mod)%mod<<" "<<(long long)(cnt);
return 0;
}

浙公网安备 33010602011771号