UESTC 1709 Binary Operations

http://www.cnblogs.com/dgsrz/articles/2575682.html


题目描述:

Bob有N个数字组成的序列。它们都是如此的吸引人,让Alice乞求获得其中的连续部分(所谓连续部分就是连续子序

列)。然而,Bob只允许Alice随机地选择。你能否求出所有选择的数字进行与、或、异或操作之后的期望?

输入:

第一行包含一个整数T(1<=T<=50),表示有T组输入案例。

每个案例的第一行包括一个整数N(1<=N<=50000)

案例第二行有N个整数,表示这个序列。所有整数都落在[0, 10^9]范围内。

输出:

对于每组测试案例,输出“Case #t: a b c”,其中t是从1开始的测试案例的编号,a是与AND运算的期望,b是或OR运算

期望,c是异或XOR运算期望。


分析:

如果本题数据范围不大,可以直接暴力,即枚举每个序列,分别进行AND\OR\XOR操作,然后求和求期望。但这样明显时间复杂度不够。为此需要进行一些优化。
注意到,对于“A1 A2 A3...”这样的案例,以A1为末尾的子序列有A1,A2为末尾的有A1 A2、A2,A3为末尾的有A3、A2 A3、A1 A2 A3……而以与运算为例,所有子序列位运算之后求和,有: A1 + [(A2&A1) + A2] + [(A3&A2&A1 + A3&A2) + A3] + …… 。显然,后一个子序列进行的操作和前一个有关。为此,考虑到数据范围0-10^9,开一个至少30位的数组num[30],存储进行到当前步骤时(即以第i个数字结尾的子序列),每一位上剩余几个1。
初始化这个数组全部为0。然后仍以与运算为例,进行如下操作:
先将这个第i个末尾化为二进制,然后每一位与上一次的状态进行比较。考虑与运算性质,如果这个末尾的二进制表示某一位 j 是0,则结果一定是0,即剩余0个1,故num[j] = 0。若第j位为1,则原来有几个1,与运算之后也有几个1,根据num[j]表示意义,其值+1。
对于或运算,当第j位为1,则num[j]就与执行次数有关(有m个数进行操作,则与运算之后这一位全是1)。假定当前进行到第i个数字,num[j] = i。若第j位为0,num[j]不变。
对于异或,当第j位为1,则num[j]和当前结果这一位有几个0有关。当前结果有几个0,有与num[j](有几个1)和执行次数(总共操作m个数)有关,故num[j]=i-num[j]。若第j位为0,则num[j]不变。
每进行完一次位运算,需要将整个序列的第i个末尾加到状态中(参考上述公式),然后求得当前第i个数为结尾的子序列进行操作后,新值的和。根据num[j]的意义,无非就是将num[j]存储的二进制转为十进制。
最后,求出所有数为结尾的子序列的和S,又因为每种情况都是等可能,求出事件总数 Q=n*(n+1)/2,得E=S/Q。得解。
进行如上优化后,每一位数字只进行30次计算,相比最初的暴力可以节省相当多的时间。



代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
#define LL long long
const long M=51000;
const long MPOS=31;
long t,n,num[3][MPOS+5];



LL ans[3];
int main(){
	scanf("%d",&t);
	for (long i=1;i<=t;++i){
		scanf("%d",&n);
		double sum=1.0*(LL)n*(n+1)/2.0;
		memset(num,0,sizeof(num));
		memset(ans,0,sizeof(ans));
		for (long j=1;j<=n;++j)
		{


			long x;
			scanf("%d",&x);



			for (long k=0;k<MPOS;++k)
			{
				if (x & (1<<k)){
					num[0][k]++;  // 其值 +1
					num[1][k]=j;
					num[2][k]=j-num[2][k];
				}
				else
					num[0][k]=0;


				for (long l=0;l<3;++l)
					ans[l]+=num[l][k]*((LL)1<<k);


			}


		}
		printf("Case #%d: %.6lf %.6lf %.6lf\n",i,ans[0]/sum,ans[1]/sum,ans[2]/sum);
	}
	return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值