Description
在区间[0,n)中随机生成一个整数x,然后,有p的概率选择[0,n)中与x异或值最大的y,否则在区间[0,n)中随机选择一个整数y。求
x⊕y
的期望值。
n<=10^18
Solution
首先把答案写出来:
Ans=1−pn2∑i=0n−1∑j=0n−1i⊕j+pn∑i=0n−1i⊕f(i)
其中 f(i) 表示[0,n)中与i异或值最大的那个数。
然后,我们把答案分成两部分来求。
Part 1
也就是相当于求
∑i=0n−1∑j=0n−1i⊕j
很显然可以用二进制来一位一位判断。
因为每一位互不影响,期望值相加即可。
一般的,区间[ 0 ,
然后,区间[ k∗2i+1 , k∗2i+1+2i )第i位为0,区间[ k∗2i+1+2i , (k+1)∗2i+1 )第i位为1。
所以,区间[0,n)中二进制第i位为1的个数是:
⌊n2i+1⌋∗2i+max(nmod2i+1−2i,0)
设为pi,那么这一位对答案的贡献为 2∗(1−p)∗pi∗(n−pi)∗2i
因为最后要除以n^2,可以先除掉,避免答案过大而爆炸。
于是把上式/n,那么对答案的贡献就是 2∗(1−p)∗pi∗(1−pi)∗2i
Part 2
也就是相当于求
∑i=0n−1i⊕f(i)
这个东西本蒟蒻暂时没有想到什么高深算法,只能上数位dp了。
设F[i,j,k],表示现在做到第i位(从高到低),x是不是达到上界,f(x)是不是达到上界的和,G[i,j,k]表示这样的数的个数。
Dp方程还是很好想的,只不过有两个数需要限制而已。。不过限制都是一样的。
Code
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
typedef double db;
typedef long long ll;
db p,ans1,ans2,f[61][2][2];
ll n,mi[61],g[61][2][2];
int a[61],tot;
int main() {
scanf("%lld%lf",&n,&p);int l=log2(n);tot=-1;
if (n==1) {printf("0.000000");return 0;}
for(ll x=n-1;x;x/=2) a[++tot]=(x&1);
mi[0]=1;fo(i,1,l+1) mi[i]=mi[i-1]*2;
fo(i,0,l) {
db f=(n/mi[i+1]*mi[i]+
max((ll)0,n%mi[i+1]-mi[i]))/(db)n;
ans1+=2*f*(1-f)*mi[i]*(1-p);
}
f[tot][0][1]=f[tot][1][0]=mi[tot];
g[tot][0][1]=g[tot][1][0]=1;
fd(i,tot,1)
fo(j,0,1)
fo(k,0,1)
if (g[i][j][k])
fo(x,0,1) {
if (!j&&x>a[i-1]) continue;
int y=0,z=0;
if (j||x<a[i-1]) y=1;
if (k||1-x<a[i-1]) z=1;
f[i-1][y][z]+=f[i][j][k]+(k||1-x<=a[i-1])*
mi[i-1]*g[i][j][k];
g[i-1][y][z]+=g[i][j][k];
}
fo(i,0,1) fo(j,0,1) ans2+=f[0][i][j]*p;
ans2/=(db)n;
printf("%.6lf",ans1+ans2);
}