這個狀態機是我理解上的狀態機,即:在設計算法的時候不只是專注於其中的數字邏輯,因爲數字邏輯雖然易於抽象,但是有時會付出極大的時間消耗。
而使用狀態機的思路可以增加內存來存儲狀態進行演繹,但是能夠大幅度減少時間消耗
例子:一個擊鼓傳花的問題:
學校聯歡晚會的時候,爲了使每一個同學都能參與進來,主持人常常會帶着同學們玩擊鼓傳花的遊戲。遊戲規則是這樣的:n個同學坐着圍成一個圓圈,指定一個同學手裏拿着一束花,主持人在旁邊背對着大家開始擊鼓,鼓聲開始之後拿着花的同學開始傳花,每個同學都可以把花傳給自己左右的兩個同學中的一個(左右任意),當主持人停止擊鼓時,傳花停止,此時,正拿着花沒傳出去的那個同學就要給大家表演一個節目。
聰明的小賽提出一個有趣的問題:有多少種不同的方法可以使得從小賽手裏開始傳的花,傳了m次以後,又回到小賽手裏。對於傳遞的方法當且僅當這兩種方法中,接到花的同學按接球順序組成的序列是不同的,才視作兩種傳花的方法不同。比如有3個同學1號、2號、3號,並假設小賽爲1號,花傳了3次回到小賽手裏的方式有1->2->3->1和1->3->2->1,共2種。
輸入共一行,有兩個用空格隔開的整數n,m(3<=n<=30,1<=m<=30)
輸出共一行,有一個整數,表示符合題意的方法數
首先:不適用狀態機的解決方案(代碼在後邊)
:遍歷每種可能的狀態,判斷是不是傳回給了自己
當傳遞次數增加的時候,狀態的可能性呈指數增加:而事實上,並不需要針對每個中間過程中的狀態來判斷,只需要最後停止時候的狀態的可能性
思路:
第一步:建立狀態陣
對於N個鏈式節點,狀態陣爲長度爲N的內存
長度爲N的一段狀態,這裏的狀態只有兩種情況,可以選擇Bool活着int,int可以統計數據,更加理想
爲了存儲m次傳遞之後的狀態,這裏擴充內存結構爲:
代碼:shared_ptr<int> MatirxCount = new int[n][m];
用智能指針管理動態數組;
第二步:逐次模擬結果狀態
1. 初始化狀態:MatrixCount[1][0] = 1;
2. 每次傳遞:將MatrixCount[i][j]向周圍擴散,每次傳遞後的數據向下一行記錄;
關鍵步驟:每次1擴散到節點都代表有一種將花傳遞到該節點的方案
3. 循環擴散m次
第三步:拿到結果並輸出
要求的是輸出傳回給自己的方案數,那麼只訪問統計m次以後,MatrixCount[1][m]的值即可
具體實現:
int main()
{
int n, m;
cin >> n >> m;
// 擴容:n+1增加一個單元形成閉環;m+1:初始狀態佔一個位置
vector<vector<int>> MatrixCount(m + 1, vector<int>(n + 2, 0));
// 開始逐次模擬
MatrixCount[0][1] = 1;
MatrixCount[0][n+1] = 1;
for (int i = 1; i <= m; ++i)
{
for (int j = 1; j <= n; ++j)
{
// 每次都將矩陣中的狀態向下擴散
MatrixCount[i][j] = MatrixCount[i-1][j-1] + MatrixCount[i-1][j+1];
}
MatrixCount[i][0] = MatrixCount[i][n]; // 用計算彌補數據結構的不足
MatrixCount[i][n+1] = MatrixCount[i][1];
}
cout << MatrixCount[m][1]; // 拿到狀態統計結果
return 0;
}
附加一個遍歷所有中間狀態的算法:在次數過多的時候無法執行
bool isComeBack(long Numbers, int circle, int times)
{
int tempSummary = 0;
for (int i = 0; i < times; ++i)
{
tempSummary += (Numbers % 2 == 1 ? 1 : -1);
Numbers /= 2;
}
if (tempSummary % circle == 0)
return true;
else
return false;
}
int main()
{
int m, n;
cin >> n >> m;
int count = 0;
for (long i = 0; i < pow((double)2, m); ++i)
{
if (isComeBack(i, n, m))
count++;
}
cout << count;
return 0;
}