[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;
}

总结

当转移需要前面的某些状态,可以将转移顺序逆序而状态顺序,此时状态必定合法,更方便转移。

原文地址:https://www.cnblogs.com/PPLPPL/p/15571022.html