hdu 4649 Professor Tian 多校联合训练的题
这题起初没读懂题意,悲剧啊,然后看了题解写完就AC了
题意是给一个N,然后给N+1个整数 接着给N个操作符(只有三种操作 即 或 ,与 ,和异或 | & ^ )这样依次把操作符插入整数之间就可以得到一个表达式
接着给出 N 给浮点数(在0~1之间表示概率 )表示的是 操作符和他右边的整数丢失的概率。 例如下面这组数据
1
1 2
&
0.5
整数与操作符间可以组成一个表达式即 1&2 但是由于某些原因表达式的操作符和他操作的右边的
那个数有一定的概率会丢失这组数据就是 &2有 0.5的概率会丢失 ,要你算这个表达式的期望值是
多少 这组数据可以这样算 当&2不丢失的时候1&2=0
当他丢失的时候表达式就变成了1 这样这个表达式的期望值就是 0*0.5+1*0.5=0.5 ;
貌似没思路啊。。。。
思路如下:
先反状态压缩——把数据转换成20位的01来进行运算因为只有20位,而且&,|,^都不会进位,那么一位一位地看,每一位不是0就是1,这样求出每一位是1的概率,再乘以该位的十进制数,累加,就得到了总体的期望。对于每一位,状态转移方程如下:
f[i][j]表示该位取前i个数,运算得到j(0或1)的概率是多少。
f[i][1]=f[i-1][1]*p[i]+根据不同运算符和第i位的值运算得到1的概率。
f[i][0]同理。
初始状态:f[0][0~1]=0或1(根据第一个数的该位来设置)
每一位为1的期望 f[n][1]
最后的结果就是把每一位为一的概率乘上这位的位权相加就可以了
下面是我的代码:
#include<cstdio> #include<bitset> #include<iostream> using namespace std; bitset<20> team[205];//强烈推荐用bitset,这个比较快而且简单好用,关键还省空间 char ch[205]; int pow[20],n,cas=0; double p[205], dp[20][205][2]; int cal(int i,char c,int j,int f=0)//f=0表示 默认是计算通过运算为0的概率,f=1则是去计算为1的概率 {//这个函数计算的运算结果概率只能是1或0,所以可以返回真假值 int ans; if(c=='&') ans=i&j; if(c=='^') ans=i^j; if(c=='|') ans=i|j; if(!f) return ans==0;//返回通过计算为0的概率 else return ans==1;//返回通过计算1的概率 } int read() { if(!(cin>>n)) return 0;//没有读入了就返回假值 for(int a,i=0;i<=n;i++) { cin>>a; team[i]=a;//用bitset转化为二进制 } for(int i=0;i<n;i++) cin>>ch[i];//读操作符 for(int i=0;i<n;i++) cin>>p[i];//读概率 return 1; } void deal() { double ans=0; for(int i=0;i<20;i++)//初始化第一位的概率 if(team[0][i]) dp[i][0][1]=1,dp[i][0][0]=0; else dp[i][0][1]=0,dp[i][0][0]=1; for(int i=0;i<20;i++) for(int j=1;j<=n;j++) { dp[i][j][1]=dp[i][j-1][1]*p[j-1]+(1-p[j-1])*(dp[i][j-1][0]*cal(0,ch[j-1],team[j][i],1)+dp[i][j-1][1]*cal(1,ch[j-1],team[j][i],1));//这个dp式如果还不懂就看一下,文章尾部我的说明。。 //计算当前为1的概率 dp[i][j][0]=dp[i][j-1][0]*p[j-1]+(1-p[j-1])*(dp[i][j-1][0]*cal(0,ch[j-1],team[j][i])+dp[i][j-1][1]*cal(1,ch[j-1],team[j][i])); //计算当前为0的概率 和为1的概率是一回事的 } for(int i=0;i<20;i++) ans+=pow[i]*dp[i][n][1];//统计结果 printf("Case %d:\n",++cas); printf("%.6lf\n",ans); } int main() { for(int i=pow[0]=1;i<20;i++) pow[i]=pow[i-1]*2;//计算二进制的位权 while(read()) deal(); return 0; }
dp[i][j][1]=dp[i][j-1][1]*p[j-1]+(1-p[j-1])*(dp[i][j-1][0]*cal(0,ch[j-1],team[j][i],1)+dp[i][j-1][1]*cal(1,ch[j-1],team[j][i],1));
其中dp[i][j][1]表示前j个操作后使第i位变成1的概率,dp[i][j-1][1]*p[j-1],表示当前的操作符和其右边的数消失后,使这位为1的概率
(1-p[j-1])*(dp[i][j-1][0]*cal(0,ch[j-1],team[j][i],1)+dp[i][j-1][1]*cal(1,ch[j-1],team[j][i],1));这整串是在算当前的操作符和其右边的数不消失的概率,所以有公因子(1-p[j-1])即表示,表达式不消失的概率,dp[i][j-1][0]*cal(0,ch[j-1],team[j][i],1)这句是在算如果上一步当前这位计算的结果是0,那么通过这步运算得到1的概率
,dp[i][j-1][0] 这是上一步算出这位结果为0的概率,cal(0,ch[j-1],team[j][i],1)这是算0和当前的操作符及其右边的数进行运算为1的概率,这个概率要么是一要么是0,dp[i][j-1][1]*cal(1,ch[j-1],team[j][i],1)这句就是算若上次运算的结果是1那么和当前这一步运算后得到的是1的概率,终于说完这个恶心的式子了。。。下面那个算0的式子就可以对应的去理解了,即dp[i][j][0]=dp[i][j-1][0]*p[j-1]+(1-p[j-1])*(dp[i][j-1][0]*cal(0,ch[j-1],team[j][i])+dp[i][j-1][1]*cal(1,ch[j-1],team[j][i]));
再附一个没有注释的代码
#include<cstdio> #include<bitset> #include<iostream> using namespace std; bitset<20> team[205]; char ch[205]; int pow[20],n,cas=0; double p[205]; double dp[20][205][2]; int cal(int i,char c,int j,int f=0) { int ans; if(c=='&') ans=i&j; if(c=='^') ans=i^j; if(c=='|') ans=i|j; if(!f) return ans==0; else return ans==1; } int read() { if(!(cin>>n)) return 0; for(int a,i=0;i<=n;i++) { cin>>a; team[i]=a; } for(int i=0;i<n;i++) cin>>ch[i]; for(int i=0;i<n;i++) cin>>p[i]; return 1; } void deal() { double ans=0; for(int i=0;i<20;i++) if(team[0][i]) dp[i][0][1]=1,dp[i][0][0]=0; else dp[i][0][1]=0,dp[i][0][0]=1; for(int i=0;i<20;i++) for(int j=1;j<=n;j++) { dp[i][j][1]=dp[i][j-1][1]*p[j-1]+(1-p[j-1])*(dp[i][j-1][0]*cal(0,ch[j-1],team[j][i],1)+dp[i][j-1][1]*cal(1,ch[j-1],team[j][i],1)); dp[i][j][0]=dp[i][j-1][0]*p[j-1]+(1-p[j-1])*(dp[i][j-1][0]*cal(0,ch[j-1],team[j][i])+dp[i][j-1][1]*cal(1,ch[j-1],team[j][i])); } for(int i=0;i<20;i++) ans+=pow[i]*dp[i][n][1]; printf("Case %d:\n",++cas); printf("%.6lf\n",ans); } int main() { for(int i=pow[0]=1;i<20;i++) pow[i]=pow[i-1]*2; while(read()) deal(); return 0; }
补充:软件开发 , C++ ,