问题描述
话说大诗人李白, 一生好饮。幸好他从不开车。
一天, 他提着酒显, 从家里出来, 酒显中有酒 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];
}