关闭

【数学期望】【NOIP模拟赛】连续段的期望

标签: 数学期望算法OI
628人阅读 评论(0) 收藏 举报
分类:

题目描述

小 N 最近学习了位运算,她发现2个数xor之后数的大小可能变大也可能变小,and之后都不会变大,or之后不会变小。于是她想算出以下的期望值:现在有N个数排成一排, 如果她随意选择一对 l,r并将下标在lr中间(包括l,r)的数(xor,and,or)之后,期望得到的值是多少呢?取出每一对l,r的概率都是相等的。小 G 认为这太 easy 了【这太imba了】,容易被你们水过去,因此你需要告诉他所有选择情况下,(xor,and,or)值的和。

输入

第一行1个正整数N
第二行N个非负整数代表数列。

输出

共两行六个数。 第一行3个数,分别表示xor的期望,and的期望,or的期望,保留3位小数。 第二行3个数,分别表示xor的和,and的和,or的和。

样例输入

2
4 5

样例输出

2.750 4.250 4.750
11 17 19

数据范围

30%数据中1N1000
对于另外的30%数据数列中只包含0和1
对于100%的数据1N100000,数列中的数109

样例解释

l, r xor and or
1,1 4 4 4
1,2 1 4 5
2,1 1 4 5
2,2 5 5 5

每一组l,r取的概率都是相同的,xor=(4+1+1+5)/4=2.750。其他同理 。


算法一

对于前30%数据,考虑暴力枚举l后扫到r并统计答案,期望则是每种运算的和除以n^2,时间复杂度O(n2),期望得分30分。

算法二

对于只包含0和1的数列,考虑三种运算的性质。

对于xor运算
  • 一段区间内如果包含奇数个1,则区间xor和为1;
  • 如果包含偶数个1,则区间xor和为0。
  • 可以发现若rl,对于每个1,其能对答案贡献的区间为以这个1及以前所有连续的0为开始,以这个1和从这个1开始统计序号为奇数的1以及他们后面连续的0为结尾的区间。可分奇偶做。
  • 举个栗子:
    0,1,1,0,0,0,1,0,0,1,0,1
  • 对于第一个1,有贡献的区间开始为{1,2},有贡献的区间结尾为{7,8,9,12}。乘起来就是贡献为8。
  • 对于每个1,可以统计出其前后的连续0个数,从前向后扫描时,可以先处理后面所有有贡献的1的后面的0的后缀和。为处理r<l的情况,将前方统计乘2减去1的个数即可(l=r)。
对于and运算
  • 一段区间内如果包含0,则区间and和为0;
  • 一段区间内如果全为1,则区间and和为1。
  • 从前向后扫描连续为1的区间,这段区间对答案的贡献就是区间长度的平方。
对于or运算
  • 一段区间如果包含1,则区间or和为1;
  • 一段区间如果全为0,则区间or和为0。
  • 从前向后扫描连续为0的区间,则有区间长度平方的区间无贡献,总贡献为n2减去区间长度平方和。
  • 可在统计xor时顺便统计出来

时间复杂度O(n),期望得分30分。
结合算法一,期望得分60分。

算法三

其实出题人已经指了一条明路。容易发现,可以对数列中的数的二进制表示的每一位分别做算法二。因为xor,and,or这些运算分别为按位运算,各数位之间无影响。
时间复杂度O(32n),期望得分100分。

上代码。

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
struct one{
    int fore, back;
}even[50001], odd[50001];
//从0开始序号为偶数和奇数的1
long long n, arr[100002], l1, l2, l3, a1, a2, a3, sufe[50000], sufo[50000];
//N,数列,当前位三种运算和,总和,序号为偶和奇的1的back后缀和
int main(){
    scanf("%lld", &n);
    for(int i=0;i<n;i++)
        scanf("%lld", &arr[i]);
    for(int k=0;k<=31;k++){
        //枚举k位
        (arr[n]=1)<<=k;
        //边界
        memset(odd, 0, sizeof odd); memset(even, 0, sizeof even); memset(sufe, 0, sizeof sufe); memset(sufo, 0, sizeof sufo);
        long long cnt=0, zeros=0; l1=l2=l3=0;
        //一的个数,连续0的个数
        for(int i=0;i<=n;i++){
            if(!(arr[i]&arr[n])) zeros++;
            else{
                l3+=zeros*zeros;
                //统计or和
                if(cnt&1)
                    odd[cnt>>1].fore=zeros+1, even[cnt>>1].back=zeros+1;
                else even[cnt>>1].fore=zeros+1, odd[(cnt>>1)-1].back=zeros+1;
                //奇偶分别处理,注意cnt
                cnt++; zeros=0;
            }
        }
        cnt--;
        sufe[cnt>>1]=even[cnt>>1].back;
        for(int i=(cnt>>1)-1;~i;i--)
            sufe[i]=sufe[i+1]+even[i].back;
        for(int i=0;i<=(cnt>>1);i++)
            l1+=even[i].fore*sufe[i];
        if(cnt&1){
            sufo[cnt>>1]=odd[cnt>>1].back;
            for(int i=(cnt>>1)-1;~i;i--)
                sufo[i]=sufo[i+1]+odd[i].back;
            for(int i=0;i<=(cnt>>1);i++)
                l1+=odd[i].fore*sufo[i];
        }
        else{
            sufo[(cnt>>1)-1]=odd[(cnt>>1)-1].back;
            for(int i=(cnt>>1)-2;i>=0;i--)
                sufo[i]=sufo[i+1]+odd[i].back;
            for(int i=0;i<(cnt>>1);i++)
                l1+=odd[i].fore*sufo[i];
        }
        //同样注意cnt的奇偶
        (l1*=2)-=cnt;
        l3=n*n-l3;
        long long ones=0; arr[n]=0;
        for(int i=0;i<=n;i++){
            if(arr[i]&(1ll<<k)) ones++;
            else l2+=ones*ones, ones=0;
        }
        //统计and和
        a1+=l1<<k; a2+=l2<<k; a3+=l3<<k;
        //统计第k位上的答案
    }
    printf("%.3lf %.3lf %.3lf\n%lld %lld %lld\n", double(a1)/(n*n), double(a2)/(n*n), double(a3)/(n*n), a1, a2, a3);
    return 0;
}

按位处理好强。

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:10247次
    • 积分:338
    • 等级:
    • 排名:千里之外
    • 原创:22篇
    • 转载:0篇
    • 译文:0篇
    • 评论:10条