P1850 換教室(期望dp,Floyd)

P1850 換教室

題目描述

有n個課程安排,其中第i個課程安排是在aia_i號教室上課. 每個課程安排都有相應的替代方案,也就是說,我們也可以選擇申請在bib_i號教室上同樣的課程. 對於每個課程安排,申請通過的概率爲pip_i,而且我們最多可以申請更換mm個課程的上課地點. 當然,也可以選擇一個都不申請. 與此同時,課間在不同教室之間移動時有一個體力消耗值,現在我們希望求一種申請更換教室的方案,使得體力消耗值的和的期望最小.

題目分析

期望的數學定義:試驗中每次可能結果的概率乘以其結果的總和.

這裏求的期望,等於一種已經確定的選擇方案中,每一步的概率(P)(P)乘以每一步的體力消耗的和.

這是一個以概率加權的平均數形式.

期望dp的基本形式:E(x)min=P1W1+P2W2+...+PnWnE(x)_{min}=P_1W_1+P_2W_2+...+P_nW_n

80pts:

觀察到測試點中有許多給出的是m=0,m=1,m=2m=0,m=1,m=2的形式,把這些拿下就有差不多80pts80pts了.

100pts:

我們不妨考慮動態規劃,設計狀態:f[i][j]f[i][j]表示當前ii個課程總共選擇了jj個進行變更時,所得到的期望最小體力消耗.

然而我們發現這樣子不足以完全表示狀態,因爲狀態還與前一個課程是否選擇更換教室有關,如果前一個課程更換了教室,那麼前後兩個課程的體力消耗值會改變. 也就是說,這樣設的狀態產生了後效性. 所幸我們知道,我們可以通過細化狀態轉移方程(即給狀態加維)來消除後效性,那麼我們就增加一維0/10/1,表示當前這個課程是否更換了教室.

接下來我們研究如何進行狀態轉移.

不妨考慮最簡單的情況,也就是i1i-1ii均不申請更換,那麼期望直接加上兩者之間的體力消耗即可.

然後考慮ii不申請更換,而i1i-1申請更換的情況,那麼這樣子i1i-1就有pi1p_{i-1}的可能成功更換,1pi11-p_{i-1}的可能不能成功更換,由於這兩種情況都有可能發生,而期望是所有可能情況的概率乘以體力消耗的和,所以這裏應該把兩者和他們對應的體力消耗的積都加上.

由上面的描述,這種情況的期望是f[i1][j][1]+p[i1]g[b[i1]][a[i]]+(1p[i1])g[a[i1]][a[i]]f[i-1][j][1]+p[i-1]*g[b[i-1]][a[i]]+(1-p[i-1])*g[a[i-1]][a[i]]

之後我們需要在以上兩個值中取minmin

f[i][j][0]=min(f[i1][j][0]+g[a[i1]][a[i]],f[i1][j][1]+p[i1]g[b[i1]][a[i]]+(1p[i1])g[a[i1]][a[i]])f[i][j][0]=min(f[i-1][j][0]+g[a[i-1]][a[i]],f[i-1][j][1]+p[i-1]*g[b[i-1]][a[i]]+(1-p[i-1])*g[a[i-1]][a[i]])

同理我們可以得到

f[i][j][1]=min(f[i1][j1][0]+p[i]g[a[i1]][b[i]]+(1p[i])g[a[i1]][a[i]](前一節課沒有申請更換),f[i1][j1][1]+p[i]p[i1]g[b[i1]][b[i]]+p[i](1p[i1])g[a[i1]][b[i]]+(1p[i])p[i1]g[b[i1]][a[i]]+(1p[i])(1p[i1])g[a[i1]][a[i]](前一節課也申請更換))f[i][j][1]=min(f[i-1][j-1][0]+p[i]*g[a[i-1]][b[i]]+(1-p[i])*g[a[i-1]][a[i]]\text{(前一節課沒有申請更換)},f[i-1][j-1][1]+p[i]*p[i-1]*g[b[i-1]][b[i]]+p[i]*(1-p[i-1])*g[a[i-1]][b[i]]+(1-p[i])*p[i-1]*g[b[i-1]][a[i]]+(1-p[i])*(1-p[i-1])*g[a[i-1]][a[i]]\text{(前一節課也申請更換)})

這一個狀態轉移方程和上面那個的原理是一樣的,只不過這裏還同時用到了乘法原理(期望+=兩個獨立事件同時發生的概率*對應的體力消耗


講完了狀態轉移方程,這道題基本上就結束了. 但是注意初始化所有狀態ff爲極大值(所有ff都只能由前一個時間段推知),然後f[1][0][0]=0,f[1][1][1]=0f[1][0][0]=0,f[1][1][1]=0(第一節課更換教室的情況和不更換教室的情況,由於一開始就在教室,所以一開始消耗的體力均爲0),枚舉的當前時間段ii從2開始循環;在每次ii循環中,都先要f[i][0][0]=f[i1][0][0]+g[a[i1]][a[i]](i>1)f[i][0][0]=f[i-1][0][0]+g[a[i-1]][a[i]](i>1)(不提出申請的情況),枚舉的已用申請次數jj從1開始循環.

程序實現

#include<bits/stdc++.h>
using namespace std;
const int inf=400000;//inf不能開太大也不能開太小
int c[2010],d[2010],n,m,cnt,e;
int g[310][310];
double f[2010][2010][2],p[2010],ans;
int main(){
	scanf("%d%d%d%d",&n,&m,&cnt,&e);
	for(int i=1;i<=n;i++)scanf("%d",&c[i]);
	for(int i=1;i<=n;i++)scanf("%d",&d[i]);
	for(int i=1;i<=n;i++)scanf("%lf",&p[i]);
	for(int i=1;i<=cnt;i++){
		for(int j=1;j<=cnt;j++)g[i][j]=(i==j)?0:inf;
	} //初始化鄰接矩陣
	for(int i=1,u,v,w;i<=e;i++){
		scanf("%d%d%d",&u,&v,&w);
		g[u][v]=min(g[u][v],w);
		g[v][u]=g[u][v];//注意雙向邊
	}
	for(int k=1;k<=cnt;k++){
		for(int i=1;i<=cnt;i++){
			for(int j=1;j<=cnt;j++){
				g[i][j]=min(g[i][k]+g[k][j],g[i][j]);
				g[j][i]=g[i][j];
			}
		}
	}//Floyd求最短路
	for(int i=1;i<=n;i++){
		for(int j=0;j<=m;j++){
			f[i][j][0]=(double)inf;
			f[i][j][1]=(double)inf;
		}
	}//狀態初始化
	f[1][0][0]=0;f[1][1][1]=0;
	for(int i=2;i<=n;i++){
		f[i][0][0]=f[i-1][0][0]+g[c[i-1]][c[i]];//每一個狀態都只能由前一個推知
		for(int j=1;j<=m;j++){//從1開始循環,防止越界
			f[i][j][0]=min(f[i-1][j][0]+g[c[i-1]][c[i]],f[i-1][j][1]+p[i-1]*g[d[i-1]][c[i]]+(1-p[i-1])*g[c[i-1]][c[i]]);
			//當前課程如果不變
			f[i][j][1]=min(f[i-1][j-1][0]+p[i]*g[c[i-1]][d[i]]+(1-p[i])*g[c[i-1]][c[i]],\
			f[i-1][j-1][1]+p[i]*p[i-1]*g[d[i-1]][d[i]]+p[i]*(1-p[i-1])*g[c[i-1]][d[i]]+(1-p[i])*p[i-1]*g[d[i-1]][c[i]]+(1-p[i])*(1-p[i-1])*g[c[i-1]][c[i]]);
			//當前課程如果更換
		}
	}
	ans=inf;
	for(int i=0;i<=m;i++)ans=min(f[n][i][1],min(ans,f[n][i][0]));
	//由於沒有要求m個申請全部用完,所以要比較查找
	printf("%.2lf\n",ans);
	return 0;
} 

題後總結

一篇概率/期望dp的入門博客

概率/期望dp,一定要走出一個誤區:不要總是想着算出一種方案的所有情況的概率再乘權值. 要用動態規劃的思維去做這些題.

比如說:已知一種方案爲改變第1節課上課地點和第2節課上課地點,那我們不應該先算出對應的四種情況的概率(1成功2成功,1成功2失敗,1失敗2成功,1、2均失敗)再去求期望,最後再比較,而是應該在狀態轉移中表示所有的狀態.

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