李白打酒加强版(31-34)

79 阅读1分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 31 天,点击查看活动详情

题目描述

话说大诗人李白,一生好饮。

幸好他从不开车。

一天,他提着酒壶,从家里出来,酒壶中有酒 22 斗。

他边走边唱:

无事街上走,提壶去打酒。

逢店加一倍,遇花喝一斗。

这一路上,他一共遇到店 NN 次,遇到花 MM 次。

已知最后一次遇到的是花,他正好把酒喝光了。

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

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

输入格式

第一行包含两个整数 NN 和 M$。

输出格式

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

数据范围

对于 40%40% 的评测用例:1N,M101≤N,M≤10
对于 100%100% 的评测用例:1N,M1001≤N,M≤100

输入样例:

5 10

输出样例:

14

样例解释

如果我们用 00 代表遇到花,11 代表遇到店,1414 种顺序如下:

010101101000000
010110010010000
011000110010000
100010110010000
011001000110000
100011000110000
100100010110000
010110100000100
011001001000100
100011001000100
100100011000100
011010000010100
100100100010100
101000001010100

题目分析

本题为十三届蓝桥杯省赛中的一道题目,考察的是线性DP的问题。

首先我们考虑暴力解法,李白遇酒店 nn 次,遇花 mm 次,总数为 n+mn+m 次,可以知道每次遇见为酒店或花,于是我们可以依次假设遍历,遍历的复杂度为 O(2n)O(2^n),可知显然无法通过所有样例。

考虑动态规划。

定义 f[i][j][k] 表示已经遇见了 ii 次酒店,jj 次花且当前剩余酒量为 kk 的所有方案数。对于 i,ji,j 的范围,显然 0in,  0jm0\le i \le n,\; 0\le j \le m,那么 kk 呢?

我们可以发现,所有方案中只有遇见花为减少酒量,而最终酒量为 00,遇见花的次数为 mm,这也便意味着 kk 的最大次数不会超过 mm,若大于 mm 则最终无法归零。

初始化 f[0][0][2] = 1,最终答案可用 f[n][m-1][1] 表示。

按本次遇见酒店或是花为状态转移,最终复杂度为 O(nm2)O(nm^2)

Accept代码

#include <bits/stdc++.h>

using namespace std;

const int N = 110, mod = 1000000007;

int n, m;
int f[N][N][N];

int main()
{
    cin >> n >> m;
    
    f[0][0][2] = 1;
    for (int i = 0; i <= n; i ++)
        for (int j = 0; j <= m; j ++)
            for (int k = 0; k <= m; k ++)
            {
                int& v = f[i][j][k];
                if (i && k % 2 == 0) v = (v + f[i - 1][j][k / 2]) % mod;
                if (j) v = (v + f[i][j - 1][k + 1]) % mod;
            }
    cout << f[n][m - 1][1];
    return 0;
}