题目描述
话说大诗人李白,一生好饮。幸好他从不开车。
一天,他提着酒壶,从家里出来,酒壶中有酒 2 斗。他边走边唱:
无事街上走,提壶去打酒。
逢店加一倍,遇花喝一斗。
这一路上,他一共遇到店 N 次,遇到花 M 次。已知最后一次遇到的是花, 他正好把酒喝光了。
请你计算李白这一路遇到店和花的顺序,有多少种不同的可能?
注意:壶里没酒 ( 0 斗) 时遇店是合法的,加倍后还是没酒;但是没酒时遇花是不合法的。
输入格式
第一行包含两个整数 N 和 M.
输出格式
输出一个整数表示答案。由于答案可能很大,输出模 1000000007 的结果。
样例输入
5 10样例输出
14提示
如果我们用 0 代表遇到花,1 代表遇到店,14 种顺序如下:
010101101000000
010110010010000
011000110010000
100010110010000
011001000110000
100011000110000
100100010110000
010110100000100
011001001000100
100011001000100
100100011000100
011010000010100
100100100010100
101000001010100
对于 40% 的评测用例:1 ≤ N, M ≤ 10。
对于 100% 的评测用例:1 ≤ N, M ≤ 100。
emmmm,题目理解起来不难,相当于对N个1、M个0共N+M个数字串的重排列,使排列满足题意
先贴一个简单的dfs
#include<iostream>
#include<vector>
#include <numeric>
#include<set>
#include <queue>
#include <unordered_map>
#include<unordered_set>
#include<math.h>
#include<algorithm>
#include<stack>
#include<string>
#include<map>
#define PI acos(-1)
using namespace std;
typedef long long ll;
const ll INF = -1;
const ll mod = 1e9 + 7;
int n, m, k;
int dfs(int a, int b, int c) { // a间店,b朵花,c斗酒
if (a == 0 && b == 0 && c == 0) { // 终态,返回1
return 1;
}
if (a < 0 || b < 0 || c <= 0 || c > b) {
// a<0 || b<0都不合法, c<=0也不合法,如果中途遇到0排序还成立的话,最后一位必定是1,矛盾
// c>b 则无论如何,最后不可能剩0斗酒
return 0;
}
// 要么遇店a减1,c翻倍,要么遇花b\c都减1,
return (dfs(a - 1, b, 2 * c)%mod + dfs(a, b - 1, c - 1)%mod)%mod;
}
int main() {
cin >> n >> m;
cout << dfs(n, m, 2);
}
上述是正确的dfs,且加上了剪枝函数
但是时间复杂度仍然是指数级别,是无法AC的
记忆化搜索
上面的简单dfs中,可以发现有很多状态都是重复计算的,而这非常浪费时间
我们可以以一定的空间去换取时间
例如一个很典型的例子,求阶乘
f(4)=4*f(3)=4*3*f(2)=4*3*2*f(1)=4*3*2*1
而f(3)=3*f(2)=3*2*f(1)=3*2*1
如果将f(3)记录,那么求f(4)的时候就不需要再次求f(3)了
#include<iostream>
#include<vector>
#include <numeric>
#include<set>
#include <queue>
#include <unordered_map>
#include<unordered_set>
#include<math.h>
#include<algorithm>
#include<stack>
#include<string>
#include<map>
#define PI acos(-1)
using namespace std;
typedef long long ll;
const ll INF = -1;
const ll mod = 1e9 + 7;
int n, m, k;
unordered_map<int, unordered_map<int, unordered_map<int, ll> >>mp;
int dfs(int a, int b, int c) {
if (mp[a][b].count(c))return mp[a][b][c];
if (a == 0 && b == 0 && c == 0) {
mp[a][b][c] = 1;
return 1;
}
if (a < 0 || b < 0 || c <= 0 || c > b) {
mp[a][b][c] = 0;
return 0;
}
ll x = dfs(a - 1, b, 2 * c);
ll y = dfs(a, b - 1, c - 1);
return mp[a][b][c] = (x + y) % mod;
}
int main() {
cin >> n >> m;
cout << dfs(n, m, 2);
}
可AC
再贴个
动态规划
#include<iostream>
#include<vector>
#include <numeric>
#include<set>
#include <queue>
#include <unordered_map>
#include<unordered_set>
#include<math.h>
#include<algorithm>
#include<stack>
#include<string>
#include<map>
#define PI acos(-1)
using namespace std;
typedef long long ll;
const ll INF = -1;
const ll mod = 1e9 + 7;
int n, m, k;
ll dp[105][105][105]; //ijk代表,路过了i间店j朵花且现在有k斗酒的路径数
int main() {
cin >> n >> m;
dp[0][0][2] = 1; // 初始状态下有一种走法
for (int i = 0; i <= n; i++) {
for (int j = 0; j <= m; j++) {
for (int k = 0; k <= m - j; k++) {
//上一次遇花有k+1斗酒,再次遇花就变成了k斗酒,所以路径数加上
if (j > 0)dp[i][j][k] += dp[i][j - 1][k + 1];
//遇店同理,但是k必须是偶数且k不等于0,还是那个道理,k=0遇店会导致最后一次不是遇花
if (!(k & 1) && k != 0 && i > 0) dp[i][j][k] += dp[i - 1][j][k >> 1];
dp[i][j][k] %= mod;
}
}
}
cout << dp[n][m][0];
}
总结
emmm
可以看出来动态规划的效率是非常高的
记忆化搜索是自顶向下
动态规划是自底向上的