《算法竞赛进阶指南》0x38概率与数学期望 Rainbow
题目链接:https://www.acwing.com/problem/content/218/
题目给出n个数,要求求从中任意取出一个区间的数求异或和、or和、and和的数学期望。当每个数只有0和1的时候是容易求的,此时只需要把这个int数分解成二进制数对每一位进行求解即可。
其中异或和的计算中,c1是从r-1开始向前扫描的[l,r-1]段有偶数个1的l取值的集合的大小,c2的定义与 c1相似,只是是奇数个1.如果扫描到当前的数是0,那么只需要保证c1++,如果当前的数是1的话就需要交换c1和c2并且c2的数量++。
代码:
#include<iostream> #include<cstdio> using namespace std; const int maxn = 100010; int a[maxn],b[maxn]; int n; double ansxor,ansor,ansand; void Rainbow(int k){//对每个数的第k位进行处理 int last[2]={0,0},c1=0,c2=0;//last表示上一个0/1在哪个给定的数的第k位出现 double w=(double)(1<<k)/n/n; for(int i=1;i<=n;i++){ b[i]=((a[i]>>k)&1); if(b[i]){//区间长度为1的情况 ansxor+=w; ansand+=w; ansor+=w; } //区间长度不为1的情况 if(!b[i]){ ansor+=w*last[1]*2; ansxor+=w*c2*2; }else{ ansand+=(i-last[0]-1)*w*2; ansor+=w*(i-1)*2; ansxor+=w*c1*2; } //为下一位左右右端点的情况计算新的c1,c2 c1++; if(b[i])swap(c1,c2); last[b[i]]=i; } } int main(){ cin>>n; for(int i=1;i<=n;i++)scanf("%d",&a[i]); for(int i=0;i<31;i++)Rainbow(i);//1e9数据之内最多有31位 printf("%.3lf %.3lf %.3lf\n",ansxor,ansand,ansor); return 0; }
每一个不曾起舞的日子,都是对生命的辜负。