这个状态机是我理解上的状态机,即:在设计算法的时候不只是专注于其中的数字逻辑,因为数字逻辑虽然易于抽象,但是有时会付出极大的时间消耗。
而使用状态机的思路可以增加内存来存储状态进行演绎,但是能够大幅度减少时间消耗
例子:一个击鼓传花的问题:
学校联欢晚会的时候,为了使每一个同学都能参与进来,主持人常常会带着同学们玩击鼓传花的游戏。游戏规则是这样的: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;
}