1. 程式人生 > >BZOJ1076[SCOI2008] 獎勵關

BZOJ1076[SCOI2008] 獎勵關

獎勵關

Description

你正在玩你最喜歡的電子遊戲,並且剛剛進入一個獎勵關。在這個獎勵關裡,系統將依次隨機丟擲k次寶物,每次你都可以選擇吃或者不吃(必須在丟擲下一個寶物之前做出選擇,且現在決定不吃的寶物以後也不能再吃)。寶物一共有n種,系統每次丟擲這n種寶物的概率都相同且相互獨立。也就是說,即使前k-1次系統都丟擲寶物1(這種情況是有可能出現的,儘管概率非常小),第k次丟擲各個寶物的概率依然均為1/n。 獲取第i種寶物將得到Pi分,但並不是每種寶物都是可以隨意獲取的。第i種寶物有一個前提寶物集合Si。只有當Si中所有寶物都至少吃過一次,才能吃第i種寶物(如果系統丟擲了一個目前不能吃的寶物,相當於白白的損失了一次機會)。注意,Pi可以是負數,但如果它是很多高分寶物的前提,損失短期利益而吃掉這個負分寶物將獲得更大的長期利益。 假設你採取最優策略,平均情況你一共能在獎勵關得到多少分值?

Input

第一行為兩個正整數k和n,即寶物的數量和種類。以下n行分別描述一種寶物,其中第一個整數代表分值,隨後的整數依次代表該寶物的各個前提寶物(各寶物編號為1到n),以0結尾。

Output

輸出一個實數,保留六位小數,即在最優策略下平均情況的得分。

Sample Input

1 2
1 0
2 0

Sample Output

1.500000

HINT
【資料規模】

1<=k<=100,1<=n<=15,分值為[-106,106]內的整數。

題解

概率與期望第一題祭。。。

這道題很容易想到狀壓dp,因為寶物只有15種,我們可以用一個數的二進位制來表示是否有某種寶物。所以可以用dp[i][j]來表示在第i個寶物掉落,寶物拾取情況為j時的期望最大得分。

而這道題因為寶物有前提限制,在順推時不方便判斷當前狀態是否有效,所以我們採用倒推,從有效狀態轉移到有效狀態就方便的多。

於是我們可以這麼轉移狀態:

1.當我們擁有當前寶物的所有前提寶物時,我們可以從吃和不吃兩個狀態轉移過來:
dp[i][j]+=max(dp[i+1][j],dp[i+1][jp[a]]+val[a])dp[i][j]+=max(dp[i+1][j],dp[i+1][j|p[a]]+val[a])

2.反之,只能不吃
dp[i][j]+=dp[i+1][j]dp[i][j]+=dp[i+1][j]

這裡巧妙的使用了位運算,當我們判斷該寶物的前提寶物是否吃到時,可以這麼判斷:
(jandcon[a])==con[a](j\ and\ con[a])==con[a]  
這表示寶物a的前提寶物是一的地方,狀態j中也都是一。

還有強制賦值時使用的jorp[a]j\ or\ p[a],p[a]中是2a12^{a-1},根據oror的性質,只要兩個數有一個是一,其結果就為一,於是我們便將第a位強行賦成了一。

另外要弄清楚期望是個什麼東西,期望可以看作是所有可能事件的概率乘以時間的權值相加。

在本題中,每種寶物出現的機率,即在本輪中每個事件的發生機率相同,都是1n\frac{1}{n},因此每次狀態轉移時我們也要將本次的總分除以n,於是每個狀態的期望得分就是+n\frac{上一個狀態的期望得分+這個狀態的得分}{n}

最後的答案就在初始情況dp[1][0]中。

程式碼
#include<bits/stdc++.h>
using namespace std;
double dp[105][66000];
int n,k,p[20],con[20],val[20];
void in()
{
	p[1]=1;
	for(int i=2;i<=16;++i)
	p[i]=p[i-1]<<1;
	int x;
	scanf("%d%d",&k,&n);
	for(int i=1;i<=n;++i)
	{
		scanf("%d",&val[i]);
		while(scanf("%d",&x)&&x)
		con[i]+=p[x];
	}
}
void ac()
{
	for(int i=k;i>=0;--i)
	for(int j=0;j<=p[n+1]-1;++j)
	{
		for(int a=1;a<=n;++a)
		{
			if((j&con[a])==con[a])
			dp[i][j]+=max(dp[i+1][j],dp[i+1][j|p[a]]+val[a]);
			else dp[i][j]+=dp[i+1][j];
		}
		dp[i][j]/=n;
	}
	printf("%.6lf",dp[1][0]);
}
int main()
{
	in();ac();
	return 0;
}