SCOI2008 奖励关

传送门

这题状态和转移都很好想,但是问题在于转移顺序。
一开始我是自然的考虑正推的……考虑到会出现一些不合法的情况,我使用bool数组来记录当前状态是否可用。但是这样出现的问题在于,因为最终要计算平均答案,很多种选择情况 它的终止集合是不同的,而答案也不是把他们简单的加起来……

于是我们考虑倒推……倒推就不会存在有状态不合法的情况了。用(dp[i][S])表示第1轮到第i−1轮内宝物是否取过的状态为S,第i轮到第k轮的最大期望得分。
这样的话转移方程就是:(dp[i][S] += max(dp[i+1][S],dp[i+1][S | (1<<(q-1))] + v[q]) / n)(dp[i][S] += dp[i+1][S] / n) 前者是可以转移,后者是不能。

答案显然是(dp[1][0]).

#include<bits/stdc++.h>
#define rep(i,a,n) for(int i = a;i <= n;i++)
#define per(i,n,a) for(int i = n;i >= a;i--)
#define enter putchar('
')
#define pr pair<int,int>
#define mp make_pair
#define fi first
#define sc second
using namespace std;
typedef long long ll;
const int M = 100005;
const int N = 10000005;
 
int read()
{
   int ans = 0,op = 1;char ch = getchar();
   while(ch < '0' || ch > '9') {if(ch == '-') op = -1;ch = getchar();}
   while(ch >='0' && ch <= '9') ans = ans * 10 + ch - '0',ch = getchar();
   return ans * op;
}

int k,n,S[20],num,x;
double dp[105][1<<16],p,v[20],ans;

int main()
{
   k = read(),n = read(),p = (double)(n);
   rep(i,1,n)
   {
      scanf("%lf",&v[i]);
      x = read();
      while(x) S[i] |= (1<<(x-1)),x = read();
   }
   per(i,k,1)
   {
      rep(j,0,(1<<n)-1)
      {
	 rep(q,1,n)
	 {
	    if((S[q] & j) == S[q])
	    dp[i][j] += max(dp[i+1][j],dp[i+1][j | (1<<(q-1))] + v[q]) / p;
	    else dp[i][j] += dp[i+1][j] / p;
	 } 
      }
   }
   printf("%.6lf
",dp[1][0]);
   return 0;
}

原文地址:https://www.cnblogs.com/captain1/p/10544421.html