蓝桥杯22省赛 李白打酒加强版(动态规划)

问题描述

话说大诗人李白, 一生好饮。幸好他从不开车。

一天, 他提着酒显, 从家里出来, 酒显中有酒 2 斗。他边走边唱:

无事街上走,提显去打酒。 逢店加一倍, 遇花喝一斗。

这一路上, 他一共遇到店 N 次, 遇到花 M 次。已知最后一次遇到的是花, 他正好把酒喝光了。

请你计算李白这一路遇到店和花的顺序, 有多少种不同的可能?

注意: 显里没酒 ( 0 斗) 时遇店是合法的, 加倍后还是没酒; 但是没酒时遇 花是不合法的。

输入格式

第一行包含两个整数 N 和 M.

输出格式

输出一个整数表示答案。由于答案可能很大,输出模 1000000007 的结果.

样例输入">样例输入">样例输入">样例输入">样例输入

5 10

样例输出">样例输出

14

问题分析:看完问题首先想到的是深搜dfs暴力(过了40%的测试用例)。紧接着想到了用动态规划(因为第i步总是在第i-1步基础上完成的)

解题思路:

深搜dfs不过多赘述。

对于动态规划:个人认为写出动态规划的思路可分为以下几步:1,设一个合适的数组 2、数组初始化 3、最终状态求解式子4、写出状态转移方程 5、状态转移方程的限制条件。接下来我们逐步分析:

1:设一个合适的数组:首先题目让我们需要去考虑的因素分别有:a.当前来到了第几步 b.遇到了多少店 c.遇到了多少花 d.还剩多少斗酒。紧接着我们看到题目最后需要我们求“已知最后一次遇到的是花, 他正好把酒喝光了的顺序”,也就是我们只需要考虑遇到了多少花,还剩多少酒即可。综上所述,得数组dp[i][j][k]表示第i步遇到了j棵花后剩余酒k的可行方案。

2:初始化:第0步且尚未遇到花且有2斗酒时,即dp[0][0][2] = 1;

3、最终状态求解式子:可以由题意知道最后一步(n+m)一定为花,而最后一步的前一步(n+m-1)需要求解,所以我们只需要求到最后一步的前一步即可,此时的花数量为m-1,酒为1斗,最终得dp[n+m-1][m-1][1]。

4、写出状态转移方程:所谓状态转移方程,个人的理解是:在第i步中,在第i-1步中找出能代替(转移)第i步状态的式子,如我在第i步时是a状态,那么我们可以列举出第i-1步中什么状态能影响a状态,例如我在第i-1步算出了仅有b、c两个状态能影响a状态的。那么未知的a状态就可以通过b+c使a状态已知。我们回到分析上:走到第i步时有两种情况:遇到店or遇到花。首先遇到店:若当前酒为k斗,那么i-1步状态的酒即为k/2斗,而花的数量不变,那么我们就可以将第i步的状态dp[i][j][k]转移成第i-1步的状态之一:dp[i-1][j][k/2]。接着若第i步遇到花,则第i-1步状态的酒即为k+1,同时花的数量为j+1,那么我们就可以将第i步的状态dp[i][j][k]转移成第i-1步的状态之一:dp[i-1][j-1][k+1]。这样我们就可以将第i步的状态转移到第i-1步的两个状态来计算。

5、状态转移方程的限制条件:状态转移方程需要考虑有没有限制,A、对于遇到店的情况,我们刚才说过第i步酒为k那么i-1步酒为k/2,而我们的酒不可能有小数点的存在,因此k/2的前提是k为偶数。(也可以反过来想:不管第i-1步的酒数为奇偶,来到第i步遇到店后酒数*2都会变成偶数)所以遇到店的前提是酒不能是奇数,即酒数为奇数时不可能同时遇到店。这样我们就得到了第一个限制条件if( k%2 == 0 )。B、对于遇到花没有限制,只需要j>=1即可,因为我们要j-1。

代码:

// dfs
//#include<iostream>
//using namespace std;
//
//long long ans = 0;
//int a[100010], tt, cnt;
//
//void dfs( int x, int n1, int m1 ){
//	
//	if( x < 0 || n1 < 0 || m1 < 0 )return;
//	
//	if( x == 0 && n1 == 0 && m1 == 0 ){
//		ans++;
		for( int i = 0; i < tt; i ++ )printf("%d",a[i]);puts("");
//		return;
//	}
//	
//	a[tt++] = 0;
//	dfs( x-1, n1, m1-1 );
//	tt--;
//	
//	if( m1 > 0 ){
//		a[tt++] = 1;
//		dfs( x*2, n1-1, m1 );
//		tt--;
//	}
//	
//}
//
//int main(){
//	
//	int n,m;
//	cin >> n >> m;
//	cnt = n+m;
//	
//	dfs( 2, n, m );
//	
//	cout << ans%1000000007;
//	
//}
//


// 动态规划
#include<iostream>
using namespace std;

int dp[210][105][105];// i,j,k分别是第i步遇到了j棵花后剩余酒k的可行方案 

int main(){
	
	int n, m;cin >> n >> m;
	
	dp[0][0][2] = 1;	// 初始化 
	
	// 分析:在第i步中遇到店和遇到花的情况可以将状态转移到i-1步遇到店和遇到花的情况
	for( int i = 1; i < n+m; i ++ )
		for( int j = 0; j < m; j ++ )
			for( int k = 0; k <= m; k ++ ){
				if( k%2 == 0 )dp[i][j][k] = (dp[i][j][k] + dp[i-1][j][k/2])%1000000007 ;
				if( j >= 1 )dp[i][j][k] = (dp[i][j][k] + dp[i-1][j-1][k+1])%1000000007 ;
			}
	
	cout << dp[n+m-1][m-1][1];
	
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值