1. 程式人生 > >【NOJ1085】【演算法實驗四】【DP_動態規劃】花生米(五)

【NOJ1085】【演算法實驗四】【DP_動態規劃】花生米(五)

1086.花生米(五)

時限:1000ms 記憶體限制:10000K  總時限:3000ms

描述

五一長假第六天,Tom在QQ上遇到了Kitty。呵呵,Kitty,在離散數學課上認識的PPMM……等等!Tom恍然大悟:自己這一生除了看帖不回之外最大的錯誤就是離散數學沒學好! 
五一長假第七天,Tom和Jerry在倉庫散步的時候發現了一堆花生米(倉庫,呵呵,倉庫…)。這次Tom制定分花生米規則如下: 
???????1、首先選出最苦的一粒花生米,放到一個瓶子裡; 
???????2、把剩下的花生米做成花生醬,Tom和Jerry輪流取一些花生醬吃掉; 
???????3、第一個取的人只能取1.0克,以後取花生醬的數量不能少於兩個人已經取過的總數量且不能超過兩個人已經取過的總數量的三倍; 
?????? 4、不能按規則3取花生醬的人必須吃掉瓶子裡的花生米; 
?????? 5、為顯示規則的公平性,Jerry可以選擇先取或者後取。 
Jerry當然希望瓶子裡的花生米被Tom吃掉。請計算,Jerry為了達到目的應該先取還是後取。

 

輸入

本題有多個測例,每個測例的輸入是一個浮點數w,w大於1.0小於等於1000.0,w最多隻有一位小數,代表花生醬的數量,單位為克。
w小於0表示輸入結束,不需要處理。

 

輸出

每個測例在單獨的一行內輸出一個整數:Jerry先取輸出1;Tom先取輸出0。


#include <iostream>
using namespace std;

double w;

int isWin[10001];	//1==必贏,0==必輸

int dp(int ate, int  cur);

int main()
{
	while(cin>>w&&w>0)
	{
		for(int i=10; i<10001; i++)
		{
			isWin[i]=-1;
		}

		cout<<1-dp(10, int(w*10)-10)<<endl;	//第一步只能先取10個=1克,然後讓對方先取
	}
	return 0;
}

int dp(int ate, int  cur)
{
	if(isWin[cur]>=0)	//表中已經記錄,當還剩cur個花生米時,先取是必贏還是必輸
	{
		return isWin[cur];
	}
	if(ate>cur)		//如果已取數目大於當前數目,那麼此時先取必輸(因為無法滿足條件)
	{
		isWin[cur]=0;
		return isWin[cur];
	}
	else
	{
        int isOppoWin=1;	//假設自己取完之後讓對方取,對方能贏

		int x;
		for(x=ate; x<=3*ate; x++)	//假設自己先取x個
		{
			//自己取完x個,此時還剩cur-x個,讓對方先取,記錄此時對方是否能贏
			isWin[cur-x]=dp(ate+x, cur-x);

			//如果還剩cur-x個時對方先取必輸,那麼令isOppoWin=0
			isOppoWin*=isWin[cur-x];

			//如果還剩cur-x個時對方先取必輸,證明當自己取x個時是必贏的
			if(isOppoWin==0)
			{
				break;
			}
		}
		isWin[cur]=isOppoWin; //如果對方必輸,那麼自己必贏;如果對方必贏,那麼自己必輸
		return isWin[cur];
	}
}

【10.26後記】

1.這道題一開始打算記錄已吃數量ate,當前還剩數量cur的狀態,然而不是超時就是記憶體超限,無奈去網上找了程式碼,雖然ac了不過總感覺不對(可能是自己沒看懂吧)。

2.今天突然恍然大悟,只需要用陣列記錄cur的狀態就好,因為ate=n-cur,根本不需要記錄。

3.本題思路是這樣的:用陣列isWin[cur]記錄當前先取的人是必輸還是必贏:當目前還剩下cur個花生米時,這時如果先取的人必贏,那麼isWin[cur]=1,如果先取的人必輸,那麼isWin[cur]=0;

假設總共有w克,令n=w*10轉換成整數;

在演算法dp開始時,可以假設自己第一個取

,只能取10個,對方第二個取,狀態轉換到了“還剩n-10個時對方先取”的狀態,呼叫演算法dp:

如果演算法dp返回0,那麼證明“還剩n-10個時對方先取”這個狀態下對方是必輸的,(對方第二個取必輸==自己第一個取必贏),此時需要輸出1代表“自己第一個取”;

如果演算法dp返回1,那麼證明“還剩n-10個時對方先取”這個狀態下對方是必贏的,(對方第二個取必贏==自己第一個取必輸), 此時需要輸出0代表“自己第二個取”,這樣自己就能贏了;

這便是下面這行程式碼的意義:

cout<<1-dp(10, int(w*10)-10)<<endl;    //第一步只能先取10個=1克,然後讓對方先取

4.在dp演算法中,思路如下:

假設還剩cur個時是自己先取,那麼自己可以取x個,x的範圍是【ate,3*ate】,

自己取完x個之後,狀態轉換成”還剩cur-x個時對方先取“,記錄一下isWin[cur-x],代表在這個狀態下對方先取必贏還是必輸;

如果isWin[cur-x]==0,證明在”還剩cur-x個時對方先取“的狀態下對方必輸,於是跳出for迴圈,證明還剩cur個時,自己只要拿了x個就是必贏的,返回1;

如果遍歷了每個x,當狀態轉移到對方時,對方都必贏,那麼自己此時不管怎麼拿都必輸,返回0.