链接:http://118.190.20.162/view.page?gpid=T132
思路:先写一个暴力搜索,然后把中间结点的状态存起来,就是记忆化搜索,状态要存搜索到的层数和选择的卡牌数,0代表不选,1代表选,可以把16个卡牌是否选的状态压缩到2的16次方的一个int数中。
注意:1.题目有一个坑点,题目里的精度是骗人的,输出精度要达到1e-10才行,1e-5的精度会WA
2.dp数组第一维度保存层数,极端情况是是16张牌,一张牌概率0.9985,其他牌概率0.0001,k最大=5,所以树的深度最深是75左右,所以开100就够,存01状压的维度,要大于2的16次方也就是65536,所以可以开70000
3.考场上脑抽了,更新了dp数组后,没有用dp数组已经保存的值,而是又dfs下去了。。然后超时
先上20分的暴力搜索代码
2022/5/9更新:
好多人私信我问我代码的问题,我先讲一下暴力递归的分析思路:
先是想一颗树,第一层是N张牌,对于第一层的每张牌,第二层再抽N张牌,以此类推,对这颗树进行搜索的时候,就是进行一次深搜,我最终的期望相当于第一层每个结点期望的加权求和,那就是抽到每张牌的概率p乘上这个结点的期望值。之后的层同理。但是发现搜索空间太大,必须记忆化,这是dp类题目。
做dp类题目先想状态的表示,这个题根据数据可以知道要用状态压缩dp,即0和1代表卡牌的有无,但是只用一维不能解决问题,当时我是先写出了暴力的代码,根据函数的参数发现层数也需要存一下,相当于一共参与了几次抽卡。
然后发现状态转移和暴力的一样,于是改动不大,只需要把状态存起来就好。
#include <bits/stdc++.h>
const int maxn=50;
using namespace std;
double p[maxn+5];
int book[maxn+5];
int N,K;
double dfs(int now,int lev,int left){
if((lev-(N-left))/K>=left)return lev;
int flag=1;
for(int i=0;i<N;i++){
if(book[i]==0)flag=0;
}
if(flag==1)return lev;
double ans=0;
for(int i=0;i<N;i++){
if(book[i]==0){
book[i]=1;
ans+=p[i]*dfs(i,lev+1,left-1);
book[i]=0;
}
else ans+=p[i]*dfs(i,lev+1,left);
}
return ans;
}
int main()
{
cin>>N>>K;
for(int i=0;i<N;i++){
cin>>p[i];
}
double ans=0;
for(int i=0;i<N;i++){
book[i]=1;
ans+=p[i]*dfs(i,1,N-1);
book[i]=0;
}
cout<<ans<<endl;
return 0;
}
然后是满分的状压+记忆化搜索代码
#include <bits/stdc++.h>
const int maxn=50;
using namespace std;
double p[maxn+5];
int book[maxn+5];
int N,K;
double dp[1000][70000];
double dfs(int lev,int left,int state){//层数,剩余牌数,01选或者不选的状态
if((lev-(N-left))/K>=left)return lev;
double ans=0;
for(int i=0;i<N;i++){
int temps;
if(book[i]==0){
book[i]=1;
temps=state|(1<<i);
if(dp[lev+1][temps]==0){
dp[lev+1][temps]=dfs(lev+1,left-1,temps);
}
ans+=p[i]*dp[lev+1][temps];
book[i]=0;
}
else{
if(dp[lev+1][state]==0){
dp[lev+1][state]=dfs(lev+1,left,state);
}
ans+=p[i]*dp[lev+1][state];
}
}
return ans;
}
int main()
{
cin>>N>>K;
for(int i=0;i<N;i++){
cin>>p[i];
}
double ans=0;
for(int i=0;i<N;i++){
book[i]=1;
int st=(1<<i);
if(dp[1][st]==0){
dp[1][st]=dfs(1,N-1,st);
}
ans+=p[i]*dfs(1,N-1,st);
book[i]=0;
}
printf("%.10f",ans);
return 0;
}
考完以后写了一个更短的。。
#include <iostream>
const int maxn=30;
using namespace std;
int N,K;
double p[maxn+5];
int book[maxn+5];
double dp[100][80000];
double dfs(int level,int have,int state){//层数,已经抽中的卡牌数,01状态
if((N-have)*K<=level-have){
return level;
}
double ans=0;
for(int i=0;i<N;i++){
if(book[i]==0){
book[i]=1;
if(dp[level+1][state+(1<<i)]==0){
dp[level+1][state+(1<<i)]=dfs(level+1,have+1,state+(1<<i));
}
ans+=p[i]*dp[level+1][state+(1<<i)];
book[i]=0;
}
else{
if(dp[level+1][state]==0){
dp[level+1][state]=dfs(level+1,have,state);
}
ans+=p[i]*dp[level+1][state];
}
}
return ans;
}
int main()
{
cin>>N>>K;
for(int i=0;i<N;i++){
cin>>p[i];
}
double ans=dfs(0,0,0);
printf("%.10f",ans);
return 0;
}