蓝桥杯 历届试题 格子刷油漆(2013决赛)动态规划

历届试题 格子刷油漆

问题描述
X国的一段古城墙的顶端可以看成 2*n个格子组成的矩形(如下图所示),现需要把这些格子刷上保护漆。
例如下图是一个长度为3,高为2的城墙
n=3的城墙
你可以从任意一个格子刷起,刷完一格,可以移动到和它相邻的格子(对角相邻也算数),但不能移动到较远的格子(因为油漆未干不能踩!)
比如:
a d b c e f 就是合格的刷漆顺序。
c e f d a b 是另一种合适的方案。
当已知 N 时,求总的方案数。当n较大时,结果会迅速增大,请把结果对 1000000007 (十亿零七) 取模。

输入格式:输入数据为一个正整数(不大于1000)
输出格式:输出数据为一个正整数。

样例输入:2 样例输出:24
样例输入:3 样例输出:96
样例输入:22 样例输出:359635897


分析:
本题的任务是在已知几个基本行走规则的前提下,求解遍历整个矩形的行走路线数量
很多同学的第一想法是搜索,但是搜索算法必然超时,因为本题的数据范围(n最大可取到1000)会使得递归树过深,因此我们不得不另寻思路。而实际上,这种走格子的题目往往和动态规划密切相关
对于题目给出的几类行走规则,我们可以很容易地联想到递推。因为对于某个格子,其走到当前可能有很多种走法,但是从另一个角度看来,以某个格子为出发点进行“刷漆”,其行走方式却是一个固定值。我们首先要做的,就是来推理这个过程,从而找到动态转移方程。
为了将整个矩形刷完,我们的起点主要分为以下两大类:
1.从四个顶点之一出发
2.从中间某个点出发


(一)从顶点出发
① 第一步走同一列的另一个格子,然后再走下一列。接着重复这个过程。如下图所示:
从顶点A出发
比如:假设从顶点A出发,那么第一步没得选,只能走向B;接着在B点时,此时就有两种选择方案,要么走向C,要么走向D。假设走向了D,那么此时D点就只能选C,接着在C点时,其又可以选择E或F……然后重复上面这个过程,直到最终走到矩形的另一侧
这种情况(一趟过去,不再返回)用数组a来表示,则可以把问题规模由a[ i ]转换成a[ i-1]
于是得到状态转移方程:a[ i ]=2×a[ i-1](乘2的原因是每次都有两种选择,如在B点可选C或D)

② 第一步走下一列,之后也还是不断地走下一列,当最终走到最后一列后再返回。返回时,由于格子的高度为2,那么在返回时,路径唯一,如下图所示:
从顶点A出发
比如:假设从顶点A出发,那么第一步可以选C或D共两种方案,假设选的是C,那么接下来又可以选择E或F……当最后到了最后一列,比如到了I,那么此时返回的路线也就唯一确定了
这种情况(一趟过去,一趟回来)用数组b来表示,则可以把问题规模由b[i]转换成b[ i-1]
于是得到状态转移方程:b[ i ]=2×b[ i-1] (乘2的原因是每次都有两种选择,如在A点可选C或D)

③ 第一步走另一列,再由该列返回前一列,然后再从前一列走向另一列的另一个格子,如下图所示:
从顶点A出发
比如:假设从顶点A出发,那么其有两条路线:A->C->B->D或A->D->B->C,假设到了点D,则D又可以有两种选择(要么到E要么到F),此时又可再重复在点A的行为,直到最终到达矩形的另一侧
显然这也属于“一趟过去,不再返回”的类型,因此也用数组a来表示,则可以把问题规模转换成a[ i-2 ]
于是得到状态转移方程:a[ i ] = 2 × 2 × a[ i-2 ] (第一个乘2是选C或D,二个乘2是选E或F)

综上便分析出了所有的从四个顶点出发的基础遍历方式
于是得到转移方程:a[ i ] = 2 × a[ i-1] + b[ i ] + 2 × 2 × a[ i-2]
由于顶点有4个,于是最终的遍历方案为:sum=4×a[ i ]


(二)从中间出发
从中间出发
假设我们从图中i=3(E点)处出发(以i为分割线,将图分为左边的ABCDEF以及右边的GHIJ),为了遍历所有格子,我们需要先走完左边的这个整体(特别注意:这里必须倒回到F才能继续走右边),然后再把右边视为以G或者H为起点的一组格子将其走完(因此这里需要乘以2,两种起点出发嘛)
分析上述的流程,可以得到从中间出发的方案数为:( b[ i ] ) × ( 2 × a[ n-i ] )
同理,我们可以先走右边的EFGHIJ,然后再走左边的ABCD
这样的方案数为:(b[ n-i+1]) × (2×a[ i - 1])
由于上面的所有起始点都是以E为出发点行走的,我们同理也可以以F为起点出发,那么从第i列开始刷漆的方法就有:[ (b[ i ])×(2×a[ n-i ])+(b[ n-i+1])×(2×a[ i - 1]) ]×2

综合一二,便得出最终的方案数sum为:sum = 4×a[n] + 2×2×( b[ i ]×a[ n-i ] + b[ n-i+1 ]×a[ i - 1] )
这便是本题的递推公式了,现在的问题是,初值呢?
由于在(一)中存在公式:a[ i ] = 2×2×a[i-2],故我们需要将a[1]、a[2]、b[1]、b[2]都给定
所以下面我们需要手动地给出i=1和2的情况:
a[1]=1; //n=1时,从顶点出发显然只有一种情况
a[2]=6; //n=2时,从顶点出发有三种情况:a[2]=2+2+2,分别是(一)中的①②③
情况①:
情况①
情况②:
情况②
情况③:
情况③
b[2]=2; //显然这种情况下只能是两种,对应上图的情况②
b[1]=1; //b这种刷漆方式至少需要两个格子(即最低i=2),所以b[1]严格意义上说来是0,但是由于b[2]=2,且存在公式b[i]=2*b[i-1],故这里反推b[1]=1


下面直接给出本题的完整代码:

#include<iostream>
using namespace std;
const int MOD=1000000007;
int main()
{
	int n;cin>>n;
	if(n==1){
		cout<<2<<endl;
		return 0;
	}
	long long a[1005],b[1005];
	a[1]=1,a[2]=6;
	b[1]=1,b[2]=2;
	for(int i=3;i<=n;i++)			//一个顶点出出发
	{
		b[i]=(2*b[i-1])%MOD;		//边操作边取余
		a[i]=(2*a[i-1]+b[i]+4*a[i-2])%MOD;
	}
	long long sum=4*a[n]%MOD;		//4个顶点(特别注意这里的sum的数据类型需要用long long,否则这里是有可能在乘以4后数据溢出的)
	for(int i=2;i<n;i++)			//从中间出发,注意i的范围是大于1小于n
		sum=(sum+4*(b[i]*a[n-i]+b[n-i+1]*a[i-1]))%MOD;
	cout<<sum<<endl;
	return 0;
 }
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值