期望DP,用记忆化搜索实现。看到15这种小数据,就应该立刻想到状态压缩。
用
f
f
f数组记录期望。现在已选的宝藏状态为
n
o
w
now
now,现在要选第
t
t
t个宝藏,那么
f
[
n
o
w
]
[
t
]
f[now][t]
f[now][t]表示这时的期望。
为了避免一些非法的情况,考虑倒推。对于记忆化搜索来说,就是一个回溯的过程。
如果当前状态为
n
o
w
now
now,现在决定第
t
t
t个宝藏,枚举到一个宝藏
i
i
i,而当前
n
o
w
now
now又满足可以取到
i
i
i,那么就在取和不取之间求
m
a
x
max
max,对应代码就是:
f
[
n
o
w
]
[
t
]
+
=
m
a
x
(
(
d
p
(
n
o
w
∣
2
i
−
1
,
t
+
1
)
+
v
a
l
[
i
]
)
/
n
,
d
p
(
n
o
w
,
t
+
1
)
/
n
)
;
f[now][t]+=max((dp(now|2^{i-1},t+1)+val[i])/n,dp(now,t+1)/n);
f[now][t]+=max((dp(now∣2i−1,t+1)+val[i])/n,dp(now,t+1)/n);
再解释一下——
取的话,就是(走这一步的情况的期望(递归求解)
+
+
+当前宝藏的贡献)
×
\times
×(走到当前情况的概率)
不取的话,就是(走这一步的情况的期望(递归求解))
×
\times
×(走到当前情况的概率)
由于期望的线性性,加起来就好了。
注意一下,虽然 n < = 15 n<=15 n<=15,但是 k < = 100 k<=100 k<=100, f f f数组要开够。。。
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int maxn=105;
const int maxm=1<<16;
int need[maxn],k;
bool vis[maxm][maxn];
double f[maxm][maxn],val[maxn],n;
inline int read(){
int f=1,x=0;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return x*f;
}
//now表示现在状态 t表示正在选第几轮
//t>k时,已经不合法了,返回0就好了。
inline double dp(int now,int t){
if(t>k) return 0;
if(vis[now][t]) return f[now][t];
f[now][t]=0,vis[now][t]=1;
for(int i=1;i<=n;++i){
if((now&need[i])==need[i])
f[now][t]+=max((dp(now|(1<<(i-1)),t+1)+val[i])/n,dp(now,t+1)/n);
else f[now][t]+=dp(now,t+1)/n;
}return f[now][t];
}
int main(){
scanf("%d%lf",&k,&n);
for(int i=1;i<=n;++i){
val[i]=(double)read();
while(int d=read()) need[i]|=1<<(d-1);
}printf("%.6lf",dp(0,1));
}