题目描述
小 N 最近学习了位运算,她发现2个数xor之后数的大小可能变大也可能变小,and之后都不会变大,or之后不会变小。于是她想算出以下的期望值:现在有N个数排成一排, 如果她随意选择一对
l,r 并将下标在l和r 中间(包括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%数据中1≤N≤1000
对于另外的30%数据数列中只包含0和1
对于100%的数据1≤N≤100000,数列中的数≤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后扫到
算法二
对于只包含0和1的数列,考虑三种运算的性质。
对于xor运算
- 一段区间内如果包含奇数个1,则区间xor和为1;
- 如果包含偶数个1,则区间xor和为0。
- 可以发现若r≥l,对于每个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;
}
按位处理好强。