《算法竞赛进阶指南》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
",ansxor,ansand,ansor);
    return 0; 
} 
原文地址:https://www.cnblogs.com/randy-lo/p/13288441.html