题目 2662: 蓝桥杯2022年第十三届省赛真题-李白打酒加强版

题目描述

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

一天,他提着酒壶,从家里出来,酒壶中有酒 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

可以看出来动态规划的效率是非常高的 

记忆化搜索是自顶向下

动态规划是自底向上的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值