1. 程式人生 > 實用技巧 >[POJ 3744] Scout YYF I 題解

[POJ 3744] Scout YYF I 題解

[POJ 3744] Scout YYF I 題解

題意:

​ 在一條有地雷的路上,你現在的起點在 \(1\) 處。在 \(n\) 個點處布有地雷。每次有 \(p\) 的概率前進一步,\(1−p\) 的概率前進 \(2\) 步。問順利通過這條路的概率。多組資料。

​ 資料範圍:\(1 \le n \le 10\)\(0.25 \le p \le 0.75\) 地雷點的座標範圍:$ [1,10^8] $,答案保留七位小數。


題解:

\(~~~~\) 顯然概率DP,設:\(dp_i\) 表示道路 \([1,i]\) 安全通過的概率 。

\(~~~~\) 則有轉移方程 \(dp_i=dp_{i-1} \times p + dp_{i-2} \times (1-p)\)

\(i\) 處無雷) 。

\(~~~~\) 直接轉移,時間 \(\mathcal{O(T\times 10^8)}\) ,發現時空都在 \(10^8\) 級別。

\(~~~~\) 甚至多組資料,直接狗帶。

\(~~~~\) 因此要考慮優化。

優化1:矩陣快速冪

\(~~~~\) 發現轉移是一維的,且最多與前兩項有關。

\(~~~~\) 構造矩陣

\[\begin{bmatrix} p & 1-p \\ 1 & 0 \end{bmatrix}\quad \]

\(~~~~\) 分段對空白部分進行矩陣快速冪即可。

\(~~~~\) 時間 \(\mathcal{O(T \log 10^8)}\)

,空間完全不用考慮。

\(~~~~\) 程式碼:沒有 不展示

優化2:數學/找規律/亂搞

\(~~~~\) 由上可知,重點在於有很多的空白部分。

\(~~~~\) 但其實空白部分在經過約 \(100\) 次DP之後數值就幾乎不會變化(即變化量遠遠小於 \(10^7\) )。

\(~~~~\) 以上的結論可以打表發現( \(p=0.5\) ):

\(~~~~\) 其實在 \(50\) 左右數值已經穩定,但考慮不可見部分可能相差過小,為穩妥我們可以將區間之間間隔 \(>100\) 的空白區域全部縮短成長度為 \(100\) 的空白區間,可以穩過。

\(~~~~\) 說白了就是可以卡精度。

\(~~~~\) 時間:\(\mathcal{O(T 100\ n)}\)

\(~~~~\) 程式碼:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int arr[25];
bool ma[5005];//儲存是否是雷 
double dp[5005];
int main() {
	int n;
	double p;
	while(~scanf("%d %lf",&n,&p))
	{
		bool flag=true;
		for(int i=1;i<=n;i++) scanf("%d",&arr[i]);	
		sort(arr+1,arr+1+n);
		for(int i=0;i<n;i++)
		{
			if(arr[i+1]-arr[i]>100)
			{
				for(int j=n;j>i;j--) arr[j]-=(arr[i+1]-arr[i]-100);
			}
		}
		dp[1]=1;
		if(!flag) continue;
		for(int i=1;i<=n;i++) ma[arr[i]]=true;
		for(int i=0;i<=arr[n];i++)
		{
			if(!ma[i]) 
			{
				dp[min(i+1,arr[n]+1)]+=dp[i]*p;
				dp[min(i+2,arr[n]+1)]+=dp[i]*(1-p);
			}
			else ma[i]=false;//隨時清零,省掉 memset 
			dp[i]=0;//同上 
		}
		printf("%.7f\n",dp[arr[n]+1]);
		for(int i=arr[n]-2;i<=arr[n]+2;i++) dp[i]=0;//最後需要清掉 
	}
	return 0;
}