NOIP2016提高A组 A题 礼物—概率状压dp
题目描述
输入格式
输出格式
样例
数据范围与提示
solution:
考场上并没有推出式子。。。
最大喜悦值就是所有Wi的和
对于10%的数据:
这是一个送分点,耐心手玩一下也能发现规律,直接1/pi就好了。
对于30%的数据:
观察到N比较小,可以考虑状压DP。设二进制状态S表示当前有哪些物品已经拿了,然后就可以转移了。具体来说:
fi=$\sum$(fj*pk)+(1-$\sum$pq)*fi+1;
然后可以列出方程组,用高斯消元求解。
时间复杂度:O(23n)
100%:
其中j状态是i状态的子集,k就是i和j相差的那一位,q是i中有的元素。前半段表示从j状态转移到k状态的期望,后半段表示i状态转移回自己的期望,因为步数多了一步所以+1。等式的两边可以消去f[i],再移项就变成了($\sum$p[q])*f[i]=$\sum$(f[j]*p[k]),($\sum$p[q]是f[i]的系数,f[i]+=p[q]*f[i])这样一来只要枚举每一个状态中的所有1就可以递推出f[(1<<n)-1]了。
代码:
#include<iostream> #include<cstdio> #include<cstring> #define MAXN 21 #define ll long long using namespace std; ll n,w[MAXN],tot_w=0,tot_s,m; double f[1<<21],p[MAXN]; int main(){ scanf("%lld",&n); m=(1<<n)-1; for(int i=1;i<=n;i++){ scanf("%lf %lld",&p[i],&w[i]); tot_w+=w[i]; } for(int i=1;i<=m;i++){ double s=0.0; f[i]=1; for(int j=1;j<=n;j++){ if(i&(1<<(j-1))){ s+=p[n-j+1]; f[i]+=p[n-j+1]*f[i&(~(1<<(j-1)))]; } } f[i]/=s; } printf("%lld\n%0.3lf\n",tot_w,f[m]); return 0; }
ps:好像正着推是错误的,但博主还是正推过了,于是博主又打了一遍倒推的,也过了,但不知道为什么正推是错的
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 using namespace std; 5 double f[1<<21],p[21]; 6 long long ans=0,n,w[21]; 7 int main(){ 8 scanf("%lld",&n); 9 for(int i=0;i<n;i++){ 10 scanf("%lf %lld",&p[i],&w[i]); 11 ans+=w[i]; 12 } 13 f[(1<<n)-1]=0; 14 for(int i=(1<<n)-2;i>=0;i--){ 15 double sum=0; 16 f[i]=1; 17 for(int j=0;j<n;j++) 18 if(~i&(1<<j)){ 19 f[i]+=p[j]*f[i|(1<<j)]; 20 sum+=p[j]; 21 } 22 f[i]/=sum; 23 } 24 printf("%lld\n%.3lf\n",ans,f[0]); 25 return 0; 26 }