bzoj3054 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和的平均数。
B部分对话的密码是数列P的and和的期望,定义类似于xor和。
C部分对话的密码是数列P的or和的期望,定义类似于xor和。
solution:
首先考虑把区间长度为1的和区间长度≥2的分开处理。
长度为1的概率为1/n²,长度≥2的概率为2/n²。
所以实际上问题就转化为了:求给定序列的每一个区间的or,xor,and和。
由于位运算是不进位的,所以按位依次考虑每一位。
假设当前考虑的是第id位,那么把原序列中每一个数的第id位取出来组成一个新的01序列。
除此之外,用一个last[0/1]记录上一次出现0/1的位置。
然后考虑从左往右依次扫描这个01序列(记为b[]),如果扫到了第i位,那么:
1、对于and运算:
//只有二者都为1才能算贡献
如果b[i]==1,那么ansand+=(1<<id)* ((i-1)-(last[0]+1)+1) *2/n/n;
如果b[i]==0,显然不需要计算贡献。
2、对于or运算:
//有一个1就好了
如果b[i]==1,那么ansor+=(1<<id)* (i-1) *2/n/n;
如果b[i]==0,那么ansor+=(1<<id)* (last[1]-1) *2/n/n;
3、对于xor运算:
可以发现,能够对xor造成影响是b中的每一个1。
这样整个序列就被1划分成了一段一段的区间,要么1,3,5…段有贡献,要么2,4,6…段有贡献。
这样的话就可以用两个变量cnt[0/1]分别记录一下上述两种区间的总长度 。
如果b[i]==1,那么ansxor+=(1<<id)* cnt[0] *2/n/n;
如果b[i]==0,那么ansxor+=(1<<id)* cnt[1] *2/n/n;
关于cnt的更新,每次先++cnt[0],如果b[i]==1,swap(cnt[0],cnt[1])即可。
至此,本题完美解决。
#include<cmath>
#include<string>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define RG register
#define IL inline
#define LL long long
#define DB double
using namespace std;
IL int gi() {
RG int x=0,w=0; char ch=getchar();
while (ch<'0'||ch>'9') {if (ch=='-') w=1;ch=getchar();}
while (ch>='0'&&ch<='9') x=x*10+(ch^48),ch=getchar();
return w?-x:x;
}
const int N=1e5+1;
int n,a[N],b[N];
DB ansor,ansxor,ansand;
IL void solve(int id) {
RG int i,las[2]={0,0},cnt[2]={0,0};
for(i=1;i<=n;++i)
if((b[i]=(a[i]>>id)&1))
ansor+=(DB)(1<<id)/n/n,ansxor+=(DB)(1<<id)/n/n,ansand+=(DB)(1<<id)/n/n;
for(i=1;i<=n;++i) {
if(!b[i]) ansor+=(DB)(1<<id)*las[1]*2/n/n;
else ansor+=(DB)(1<<id)*(i-1)*2/n/n,ansand+=(DB)(1<<id)*(i-las[0]-1)*2/n/n;
ansxor+=(DB)(1<<id)*cnt[b[i]^1]*2/n/n;
++cnt[0],las[b[i]]=i;
if(b[i]) swap(cnt[0],cnt[1]);
}
}
int main()
{
RG int i;
for(i=1,n=gi();i<=n;++i) a[i]=gi();
for(i=0;i<32;++i) solve(i);
printf("%.3lf %.3lf %.3lf\n",ansxor,ansand,ansor);
return 0;
}