(bonus)
題目描述
小G進入了一個神奇的世界,在這個世界,天上會掉下一些餡餅。今天,天上會隨機掉下k個餡餅。
每次天上掉下餡餅,小G可以選擇吃或者不吃(必須在下一個餡餅掉下來之前作出選擇,並且現在決定不吃的話以後也不能吃)。
餡餅有n種不同的餡,根據物理定律,天上掉下這n種餡餅的概率相同且相互獨立。然而,每一種餡餅i都有一個前提餡餅集合Si。只有當 Si 中的餡餅都吃過之後,才能吃第i種餡餅。比如說,韭菜餡餡餅的S中有白菜豬肉餡餅和鮮蝦餡餅,那麼小G只有在吃過白菜豬肉餡餅和鮮蝦餡餅之後,才能吃韭菜餡的餡餅。
同時,每個餡餅還有一個美味值Pi。今天一天小G的幸福度,等於小G吃到的所有餡餅的美味值之和。注意:Pi 可能是負數。
現在考慮,採用最優策略的前提下,小G這一天期望的幸福度是多少?
輸入格式(bonus.in)
第一行兩個正整數k和n,表示餡餅的數量和種類。
以下n行,每行若干個數,描述一種餡餅。其中第一個數代表美味值,隨後的整數表示該餡餅的前提餡餅,以0結尾。
輸出格式(bonus.out)
輸出一個實數,保留6位小數,即在最優策略下期望的幸福度。
輸入樣例
1 2
1 0
2 0
1 2 0 2
輸出樣例
1.500000
數據範圍
對於20% 的數據,所有的餡餅都沒有“前提餡餅”。
對於50% 的數據,1 ≤ k ≤ 10,1 ≤ n ≤ 10。
對於100%的數據,1 ≤ k ≤ 100,1 ≤ n ≤ 15,美味度爲[-106; 106]的整數。
題解:對於每一種餡餅,用二進制位上的1來表示是否出現,由於前面是否吃某種餡餅對後面吃某些餡餅是有影響的,所以採用倒着推的方式。倒着推的dp基本可以用記憶化搜索,當前狀態擴展出其他狀態,由於最終擴展出來的狀態非常多不好統計答案,所以纔有回溯時從末狀態返回初狀態。
總結:倒着推的dp通常都可以記憶化搜索,其實dp就是記憶化搜索,理論上dfs可以替代dp,但一定要搞清楚記憶的是什麼,值得注意的是沒記錄狀態是否已經計算過一定要用bool類型進行判斷,不能通過該狀態是否有值來判斷(超時)(很玄學)。
記憶化搜索:
//記憶化dfs代替狀態壓縮dp倒着推
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
int n,t;
using namespace std;
int date[20][20],num[20],val[20];
double dp[200][500000];
int pre[2000];
bool vis[200][300000];
double dfs(int dep,int mat_)
{
if(vis[dep][mat_]) return dp[dep][mat_];//非常重要的剪枝!!!
if(dep>t) return 0.00;
vis[dep][mat_]=1;
for(int i=1;i<=n;i++)
if( ( mat_ & pre[i] ) == pre[i] )
{
if(val[i]>=0) dp[dep][mat_]+=(double)(dfs(dep+1,mat_|(1<<(i-1)))+(double)val[i]);
else dp[dep][mat_] += max( dfs(dep+1,mat_) , (double)( dfs(dep+1,(mat_|(1<<(i-1))) ) + (double)val[i]) );
}else dp[dep][mat_]+=dfs(dep+1,mat_);
dp[dep][mat_]/=(double)n;
return dp[dep][mat_];
}
int main()
{
// freopen("bonus.in","r",stdin);
// freopen("bonus.out","w",stdout);
memset(pre,0,sizeof(pre));
int x;
scanf("%d %d",&t,&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&val[i]);
while(scanf("%d",&x) && x) pre[i] |= (1<<(x-1));
}
printf("%.6lf",dfs(1,0));
return 0;
}
/*
44 15
-221964 12 9 0
558098 3 9 0
-765634 4 8 12 10 5 0
-113034 9 0
711128 6 12 0
-610384 12 0
-280873 13 6 10 0
705891 11 5 10 9 0
112653 6 10 0
-635146 5 8 2 15 3 0
-85056 4 0
-196879 0
105600 0
768292 5 11 7 0
-328587 4 0
*/
dp:
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#define N 500000
using namespace std;
double dp[105][N];
int pre[20],val[20];
int t,n;
int main()
{
// freopen("bonus.in","r",stdin);
// freopen("bonus.out","w",stdout);
int x;
scanf("%d%d",&t,&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&val[i]);
while(scanf("%d",&x) && x) pre[i]|=(1<<(x-1));
}
for(int i=1;i<=t;i++)
for(int j=0;j < (1<<n);j++)
{
for(int k=1;k<=n;k++)
if( (pre[k]&j) == pre[k] ) dp[i][j]+=max(dp[i-1][j],dp[i-1][j|(1<<(k-1))]+(double)val[k]);
else dp[i][j]+=dp[i-1][j];
dp[i][j]/=(double)n;
}
printf("%.6lf",dp[t][0]);
return 0;
}