[动态规划&位运算]表达式的期望值

描述

给定如下表达式:

A0O1A1O2A2O3A3… OnAn

其中Ai(0<=i<=n)代表操作数,Oi(1<=i<=n)代表算子。有三类算子包括‘&’、‘|’和‘^’,这些算子拥有相同的计算优先级。每个算子Oi以及它后面相邻的操作数Ai,他们可能一起消失,消失的概率为Pi(注意为保证计算精度须使用double型数据)。

例如,对于样例输入中的第一组测试数据期望计算方法为:

(P1*P2)*A0+(1-P1)*(P2)*(A0O1 A1)+(P1)*(1-P2)*(A0O2A2)+(1-P1)*(1-P2)*( A0O1A1O2A2)=0.1*0.2*1+0.9*0.2*(1^2)+0.1*0.8*(1^3)+0.9*0.8*(1^2^3)=0.72

输入

输入包含若干测试样例。
对于每组测试样例,第一行为正整数n ( 0 < n <= 200 )
第二行包含n+1个正整数代表操作数集合{A}(其中Ai小于2^20),
第三行包含n个算子代表操作算子集合{O},第四行包含n个浮点数代表概率集合{P}(其中0 <= Pi <= 1)。

输出

对于每组测试样例,第一行输出测试样例序号(从1开始)。然后,输出期望值,近似到小数点后6位。

样例输入

2 
1 2 3 
^ ^ 
0.1 0.2 
2 
8 9 10 
^ ^ 
0.5 0.78
1 
1 2 
& 
0.5

样例输出

Case 1: 
0.720000 
Case 2: 
4.940000 
Case 3: 
0.500000
解题分析

首先,稍稍分析一下题目的运算的数量级,我们可以发现2^200是一个非常大的数,所以直接排除枚举法。那还有啥办法呢?我们注意到,在这里的每一个运算都是位运算,而位运算有一个很有意思的特点就是位间独立性,而且题目不是也提示了吗,每个数不超过2^20,所以我们只要考虑这20位就可以了!那么,问题就变成了一堆0,1序列了,我们不妨设每个数的二进制表示的第i位为ai,那么我们不妨先考虑一下只有0,1两个值的一个分布列,那么期望就是为1的概率。回到这个问题上来,其实我们可以考虑一下它们直接的递推关系,设Xk为到第k个数时,整个序列的期望,那么,由于结果只有0和1,为他们两个的二值分布,所以实际上Xk=表达式的值为1的概率。那么,显然有,当第k+1个数的运算符和数字都被吃掉的时候,有prob[k+1]的概率,这个时候,Xk+1的值就是Xk,第二种情况就是,运算符没有被吃掉,那么分别分为三种情况讨论,我们用一个calculate函数去表示,Xk+1=prob[k+1]*Xk+(1-prob[k+1])*Xk+1; 此处的Xk+1可以定义为calculate(E,ops[k+1],(nums[k+1]>>i)&1)。 注意,calculate函数返回的也是一个概率(或者说期望),即Xk+1。有时候最后一个数字可以决定整个表达式的值,比如肯定为1或者肯定为0这样子。最后我们把每一位乘上相应的2的某个次方相加即可。

代码实现
#include <iostream>
#include <cmath>
//#include <iomanip>
#include <string>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <list>
#include <bitset>
#include <queue>
#include <stack>
#include <cstdlib>
using namespace std;

int nums[205];

char ops[205];

double probs[205];

double calculate(double l,char op,double r){
	if(op=='&'){
		return r?l:0;
	}
	else if(op=='|'){
		return r?1:l;
	}
	else if(op=='^'){
		return r?1-l:l;
	}
	return 0;
}

int main(){
	int n,t=0;
	
	while(scanf("%d",&n)!=EOF){
		t++;
		printf("Case %d:\n",t);
		double res=0;
		for(int i=0;i<=n;i++){
			scanf("%d",&nums[i]);
		}
		for(int i=1;i<=n;i++){
			scanf(" %c",&ops[i]);
		}
		for(int i=1;i<=n;i++){
			scanf("%lf",&probs[i]);
		}
		for(int i=0;i<=20;i++){
			double E=(double)((nums[0]>>i) & 1);
			for(int j=1;j<=n;j++){
				E=probs[j]*E+(1-probs[j])*calculate(E,ops[j],(nums[j]>>i) & 1);
			}
			res += E * (double)(1<<i);
		}
		printf("%.6lf\n",res);
	}
	
	return 0;
}

这个做法非常巧妙,这里也很感谢一个学长or学姐提供的原理做法,这道题还是蛮有难度的!

@

逸修竹榭

  • 9
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值