[SCOI2008] 奖励关
前言
高筑墙,广积粮,缓称王。
题目
讲解
此部分与正解无关。
首先它与前面吃了哪些有关系,所以我想的是了令 \(dp_{i,S}\) 表示前 \(i\) 天吃了状态为 \(S\) 的宝物的最大期望值。
问题来了,怎么转移,你从前面转移过来的状态可能不存在,而我们要求的又是期望,状态数量都不知道怎么求期望呢?
然后我又想到刚做的这道题
既然吃了哪些做不了,那么我把状态改为前 \(i\) 天选择吃和不选择吃状态为 \(S\) 的宝物。
但是这样更不可做了,因为,你连你吃了哪些都不知道,只知道要吃哪些,无法转移。
我们再尝试修锅,令 \(f_{i,S}\) 表示前 \(i\) 天打算吃状态为 \(S\) 的宝物,\(g_{i,S}\) 表示前 \(i\) 天吃了状态为 \(S\) 的宝物的最大期望值,然而 \(g\) 和刚开始的错误思路一样,无法转移,然后我又想到自己枚举,但是算了一下过不去,至此我嗝屁了。
正解竟然是逆推。
不过想想也挺合理的,因为倒着的状态一定是合法的,令 \(dp_{i,S}\) 表示前 \(i-1\) 天吃了的宝物状态为 \(S\),\(i\) 到 \(k\) 天的最大期望得分。
那么答案就是 \(dp_{1,\varnothing}\),时间复杂度 \(O(kn2^n).\)
代码
精简
//12252024832524
#include <bits/stdc++.h>
#define TT template<typename T>
using namespace std;
typedef long long LL;
const int MAXN = 15;
const int MAXK = 105;
const int INF = 0x3f3f3f3f;
int k,n;
int p[MAXN],pre[MAXN];
LL Read()
{
LL x = 0,f = 1; char c = getchar();
while(c > '9' || c < '0'){if(c == '-') f = -1;c = getchar();}
while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();}
return x * f;
}
TT void Put1(T x)
{
if(x > 9) Put1(x/10);
putchar(x%10^48);
}
TT void Put(T x,char c = -1)
{
if(x < 0) putchar('-'),x = -x;
Put1(x); if(c >= 0) putchar(c);
}
TT T Max(T x,T y){return x > y ? x : y;}
TT T Min(T x,T y){return x < y ? x : y;}
TT T Abs(T x){return x < 0 ? -x : x;}
double dp[MAXK][1<<15];
int main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
k = Read(); n = Read();
for(int i = 0,val;i < n;++ i)
{
p[i] = Read();
while(val = Read()) pre[i] |= 1<<(val-1);
}
for(int i = k;i >= 1;-- i)
for(int S = 0;S < (1<<n);++ S)
{
for(int j = 0;j < n;++ j)
if((pre[j] & S) == pre[j]) dp[i][S] += Max(dp[i+1][S],dp[i+1][S|(1<<j)] + p[j]);
else dp[i][S] += dp[i+1][S];
dp[i][S] /= n;
}
printf("%.6f\n",dp[1][0]);
return 0;
}
总结
当转移需要前面的某些状态,可以将转移顺序逆序而状态顺序,此时状态必定合法,更方便转移。