【NOIP2013模拟】Rainbow的信号

Description

Freda发明了传呼机之后, rainbow 进一步改了传呼机发送信息所使用的号。 由于现在是数字、信息时代, rainbow 发明的信号用 N个自然数表示。 为了避免两个人的对话被 大坏蛋 VariantF偷听 T_T,rainbow 把对话分成 对话分成A、B、C三部分 ,分别用 a、b、c三个密码 加密 。现在 Freda接到了 rainbow的信息,她首要工作就是解密。 Freda了解到,这三部 分的密码计算方式如下:

在 1~N这 N个数 中,等概率地选取两个数l、r,如果 l>r,则交换 l、r。把信号中的第 l个数到第 r个数 取出来,构成一数列 P。

A部分对话的密码是数列 P的 xor 和的数学 期望值 。xor 和就是 数列 P中各个数异或之 后得到的数 ;xor和的期望 就是对于所有可能选取 的 l、r,所得到的 数列xor和的平均数 。

A部分对话 占接收到的信息总量 的 40% ,因此 如果你计算出密码 a,将获得该测试点40% 的分数。

B部分对话的密码是数列 P的 and 和的期望 ,定义类似于 xor和,占信息总 量的 30% 。

C部分对话的密码是数列 P的 or 和的期望 ,定义类似于 xor 和,占信息总量的 30% 。

Input

第一行 一个正整数 N。

第二行 N个自然数,表示 Freda接到的信号 。

Output

一行三个实数, 分别表示 xor 和、 and 和、 or 和的期望 ,四舍五入保留 3位小数, 相邻 两个 实数之间用不少于一个空格隔开 。三个实数分别占该 测试点 40% 、30% 、30% 的分数, 如果你的输出少于三个实数 ,或者 你的输出不合法,将被判 0分。

Sample Input

输入1:

2

4 5

输入2:

3

1 0 1

Sample Output

输出1:

2.750 4.250 4.750

输出2:

0.667 0.222 0.889

Data Constraint

对于 20% 的数据, 1<=N<=100

对于 40% 的数据, 1<=N<=1000

对于另外 30% 的数据, N个数为 0或 1

对于 100% 的数据, 1<=N<=100000 ,N个自然数均不超过 10^9

Algorithm:位运算、动态规划

由于各位之间互不影响,因此可以分成 31 位分别处理,这样实际上每位只有 0 和 1,就是N 个数要么是 0 要么是 1,求 and/or/xor 和的期望了,最后把各个位合起来就好。然后怎么求 N 个数 (0 或 1) 的 and/or/xor 和的期望呢?

先枚举右端点,然后往左扫描,可以发现一旦遇到一个 0,那么后面的 and 和就都是 0 了。因此对于 and 和,只需要统计每个数左边第一个 0 出现在什么位置!

同理,一旦遇到一个 1,那么后面的 or 和就都是 1 了。
因此对于 or 和,只需要统计每个数左边第一个 1 出现在什么位置!

显然是可以 O(n)统计的,肿么样,or 和、and 和的计算是不是超级简单>_~

对于 xor 和呢?可以发现,枚举右端点然后向左扫描的过程中,遇到 0,xor 和不变,遇到1,xor 和会取反。所以我们以所有的 1 为分界点,把这些数分成几段,要累加的要么就是所有奇数段中的数为左端点的 xor 和之和,要么就是所有偶数段中的数为左端点的 xor 和之和。因此我们只需要两个变量分别记录,枚举右端点的过程中,遇到 1 就交换 x 和 y,然后根据当前情况取 x、y 中合适的一个累加到答案里就可以了~ 这也是 O(n)的。

CODE

#include <cstdio>
#include <iostream>
#include <cmath>
#include <cstring>
#include <algorithm>
#define fo(i,a,b) for (int i=a;i<=b;i++)
#define N 100005
using namespace std;
int n;
double Ans_xor=0,Ans_and=0,Ans_or=0;
int Xor_number[2],Left[2];
int a[N],b[N];
void work(int x)
{
    fill(Xor_number,Xor_number+2,0);
    fill(Left,Left+2,0);
    fo(i,1,n) b[i]=((a[i]>>x)&1);
    fo(i,1,n)
    {
        if (i!=1)    
        {
            Ans_xor+=(double)(1<<x)/n/n*Xor_number[!b[i]];
            if (b[i]==0) Ans_or+=(double)(1<<x)/n/n*Left[1];    
            else
            {
                Ans_or+=(double)(1<<x)/n/n*(i-1);
                Ans_and+=(double)(1<<x)/n/n*(i-1-Left[0]);
            }   
        }
        Left[b[i]]=i;
        if (b[i]==0) Xor_number[0]++;
        else swap(Xor_number[0],Xor_number[1]),Xor_number[1]++; 
    }
    fo(i,1,n) 
    if (b[i])
    {
        double tmp=(double)(1<<x)/n/n/2;
        Ans_xor+=tmp;
        Ans_and+=tmp;
        Ans_or+=tmp;
    }
}
int main()
{
    scanf("%d",&n);
    fo(i,1,n) scanf("%d",&a[i]);
    fo(i,0,30) work(i);
    printf("%.3lf %.3lf %.3lf\n",Ans_xor*2,Ans_and*2,Ans_or*2);
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值