分析:
看到数据范围 n < = 15 n <= 15 n<=15 就很容易想到状压DP:用 f i , S f_{i,S} fi,S表示前 i i i个宝物在是否取过的状态为S的情况下的最大期望得分。
但仔细一想会发现前面的选择会影响到后面的每个宝物是否能取,即不满足DP的无后效性原则。所以,我们将状态改为用 f i , S f_{i,S} fi,S表示前 i − 1 i - 1 i−1个宝物在是否取过的状态为S的情况下第 i i i到第 k k k轮的最大期望得分。这样就可以倒序枚举第一维,满足无后效性原则。
转移时对于每一个 f i , S f_{i,S} fi,S,枚举当前物品 j j j,显然有两种情况(我们用 s i s_i si表示宝物 i i i的前提宝物集合, a i a_i ai表示宝物 i i i的权值):
1、当前取不到 j j j,即 S & s j ≠ s j S\,\&\,s_j \not= s_j S&sj=sj时:
只能由不取转移,即:
f
i
,
S
=
f
i
,
S
+
f
i
+
1
,
S
f_{i,S} = f_{i,S} + {f_{i + 1,S}}
fi,S=fi,S+fi+1,S
2、当前可以取 j j j,即 S & s j = s j S\,\&\,s_j = s_j S&sj=sj时:
选择取和不取中的最大值转移:
f
i
,
S
=
f
i
,
S
+
m
a
x
(
f
i
+
1
,
S
,
f
i
+
1
,
S
∣
2
j
−
1
+
a
j
)
f_{i,S} = f_{i,S} + {max(f_{i + 1,S},f_{i + 1,S \, | \,2 ^ {j - 1}} + a_j)}
fi,S=fi,S+max(fi+1,S,fi+1,S∣2j−1+aj)
因为求的是期望,而每一种宝物都只有
1
n
\frac1n
n1的可能性,所以最后要除以
n
n
n
最终答案即为 f 1 , 0 f_{1,0} f1,0,详见代码
Code:
#include <iostream>
#include <cstdio>
using namespace std;
const int maxn = 16,maxm = 150,inf = 1e9;
int n,k,t,a[maxn],s[maxn];
double f[maxm][1 << maxn];
int read(){
int x = 0,f = 1;
char c = getchar();
while(c < '0' || c > '9'){
if(c == '-') f = -1;
c = getchar();
}
while(c >= '0' && c <= '9') x = x * 10 + (c ^ 48),c = getchar();
return x * f;
}
int main(){
k = read(),n = read();
for(int i = 1; i <= n; i ++){
a[i] = read();
while(t = read()) s[i] |= 1 << (t - 1);
}
for(int i = k; i >= 1; i --){
for(int S = 0; S < (1 << n); S ++){
for(int j = 1; j <= n; j ++){
f[i][S] += max(f[i + 1][S],(S & s[j]) == s[j] ? f[i + 1][S | (1 << (j - 1))] + a[j] : -inf);//取不到就赋值为负无穷
}
f[i][S] /= n;
}
}
printf("%.6lf\n",f[1][0]);
return 0;
}