动态规划for循环和迭代法中的顺序问题

P1057 [NOIP2008 普及组] 传球游戏

(题源洛谷)

题目描述

上体育课的时候,小蛮的老师经常带着同学们一起做游戏。这次,老师带着同学们一起做传球游戏。

游戏规则是这样的:nn个同学站成一个圆圈,其中的一个同学手里拿着一个球,当老师吹哨子时开始传球,每个同学可以把球传给自己左右的两个同学中的一个(左右任意),当老师再次吹哨子时,传球停止,此时,拿着球没有传出去的那个同学就是败者,要给大家表演一个节目。

聪明的小蛮提出一个有趣的问题:有多少种不同的传球方法可以使得从小蛮手里开始传的球,传了mm次以后,又回到小蛮手里。两种传球方法被视作不同的方法,当且仅当这两种方法中,接到球的同学按接球顺序组成的序列是不同的。比如有三个同学11号、22号、33号,并假设小蛮为11号,球传了33次回到小蛮手里的方式有11->22->33->11和11->33->22->11,共22种。

输入格式

一行,有两个用空格隔开的整数n,m(3 \le n \le 30,1 \le m \le 30)n,m(3≤n≤30,1≤m≤30)。

输出格式

11个整数,表示符合题意的方法数。

输入输出样例

输入3 3 输出 2

好,这道题目其实一看思路就比较清楚,一般暴力搜索和正常递归能解决

先给个正常递归

#include"bits/stdc++.h"
using namespace std;

int n, m;

int answer = 0;//方法数

void Movetheball(int x, int ans)
{
	if (ans == m)
	{
		if (x == 0) 
		{
			answer++;
			ans = 0;
		}
			return;
	}
	if (x - 1 >= 0 && x + 1 < n)
	{
		Movetheball(x + 1, ans + 1);
		Movetheball(x - 1, ans + 1);
	}
	else if (x == n-1) {
		Movetheball(0, ans + 1);
		Movetheball(x - 1, ans + 1);
	}
	else if (x == 0) {
		Movetheball(x + 1, ans + 1);
		Movetheball(n-1, ans + 1);
	}
}

int main()//快乐主函数
{
	cin >> n >> m;

	Movetheball(0, 0);

	cout << answer << endl;
    
    return 0;
}

但是我们发现时间上达不到要求,会有tle,因而我选择用动态规划。

按步骤来

一:确定状态,有n个人,那我假设小蛮是0号,于是我们就有了0到n-1的编号

一共传m次,并且要回到小蛮手上,所以我用F[0][m]来表示,经过m次传球传到0号小蛮手上的方法数,这就是最后的答案;

二:状态转移方程,我们可以预见,最后一次传球是从1号或者n-1号同学传到0号小蛮手里的

所以可以列出方程F[0][m] = F[1][m - 1] + F[n-1 ][m];

然后由于同学们围成了一个环,所以要特殊考虑头尾。

三:边界条件,这是一个关键点,其实就是F[0][0]=1,代表了一次也不传,球一开始在小蛮手里,那就只有一种。

然后看上去就可以愉快地给出答案了。

但是!这里我发现一个我曾经不怎么注意的,也就是双重循环的顺序问题。思域平时就按惯性的思维i,j谁在上谁在下没啥问题,但是显然这是需要考虑的。

先给大家看两段代码

for (int j = 1; j <= m; j++)
		for(int i=0;i<n;i++)	
		{
			if (i - 1 >= 0 && i + 1 < n)
			{
				F[i][j] = F[i - 1][j - 1] + F[i + 1][j - 1];
			}
			else if (i == n - 1) {
				F[i][j] = F[i - 1][j - 1] + F[0][j - 1];
			}
			else if (i == 0) {
				F[i][j] = F[n - 1][j - 1] + F[1][j - 1];
			}
		}
for(int i=0;i<n;i++)
		for (int j = 1; j <= m;j++)
		{
			if (i - 1 >= 0 && i + 1 < n)
			{
				F[i][j] = F[i - 1][j - 1] + F[i + 1][j - 1];
			}
			else if (i == n - 1) {
				F[i][j] = F[i - 1][j - 1] + F[0][j - 1];
			}
			else if (i == 0) {
				F[i][j] = F[n - 1][j - 1] + F[i + 1][j - 1];
			}
		}

大家可以看一下,显然不同点在于i,j的先后问题,谁在内层谁在外层。

可是其实仔细思考一下便可知道,如果想第二段代码一样,那么显然我们需要的答案F[0][m]在等式左边只出现了一次,那就一次外层第一次循环内层第三次循环,那么显然:第一如果这样是对的,那我们要后面的循环干什么;第二,我们需要的答案应该是在最后几次或者最后一次循环中,被算出来;第二,从实际上来说,真正约束这个题目的只有两点,一个是要传回小蛮手里,第二个是只能传m次,那么第一个其实我们对我们的状态转移不产生影响,所欲第二次才是整体的约束,那它当然应该放在外层啦。

AC代码

+

#include"bits/stdc++.h"
using namespace std;
int main()
{
	cin >> n >> m;

	int F[31][31];

	memset(F, 0, sizeof(F));

	F[0][0] = 1;
	
	for (int j = 1; j <= m; j++)
		for(int i=0;i<n;i++)	
		{
			if (i - 1 >= 0 && i + 1 < n)
			{
				F[i][j] = F[i - 1][j - 1] + F[i + 1][j - 1];
			}
			else if (i == n - 1) {
				F[i][j] = F[i - 1][j - 1] + F[0][j - 1];
			}
			else if (i == 0) {
				F[i][j] = F[n - 1][j - 1] + F[1][j - 1];
			}
		}

	cout << F[0][m] << endl;

	return 0;

}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值