天上掉餡餅 期望dp+狀壓dp

天上掉餡餅
(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;
} 



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章