美丽序列(四维动态规划)

1. 问题描述:

牛牛喜欢整数序列,他认为一个序列美丽的定义是
1:每个数都在0到40之间 
2:每个数都小于等于之前的数的平均值
具体地说:for each i, 1 <= i < N,  A[i] <= (A[0] + A[1] + ... + A[i-1]) / i.
3:没有三个连续的递减的数
现在给你一个序列,每个元素是-1到40,你可以将序列中的-1修改成任意的数,你可以得到多少个美丽序列,答案对1e9+7取模

输入描述:
第一行输入一个整数n (1 ≤ n ≤ 40)
第二行输入n个整数

输出描述:
输出一个整数

示例1
输入
2
3 -1
输出
4

示例2
输入
3
5 3 -1
输出
2

示例3
输入
3
-1 0 40
输出
0

示例4
输入
11
-1 40 -1 -1 -1 10 -1 -1 -1 21 -1
输出
579347890
备注:
子任务1: n <= 10
子任务2: n <= 20
子任务3: 无限制

链接:https://ac.nowcoder.com/acm/problem/21313
来源:牛客网

2. 思路分析:

① 对于子序列的相关计数问题都是可以尝试使用动态规划的思路解决(尝试将当前的序列添加到之前序列的后面),对于这种题目也是如此。虽然感觉应该可以使用动态规划的思路解决,但是一开始的时候感觉题目还是挺复杂的,没有什么具体的思路后面查找了一下牛客的题解,发现可以使用四维dp解决,看了题解之后发现代码还是写得挺好的,容易理解。后面我将博主的代码改写为了python代码,不过提交上去超时了,下面是我自己对博主题解的理解。

② 因为使用的是python语言,所以需要声明一个四维的dp列表,python中声明多维列表其实可以在声明二维列表上进行扩展到三维列表然后到四维列表,具体可以参照这一片博客,对于c/c++/java可以使用四维数组解决。感觉对于一般的dp问题来说,可以首先要分析题目中涉及到变化的参数有几个,这些变化的参数往往会影响状态之间转移的dp值,对于题目中动态变化的参数有几个那么我们一般声明多少维的dp列表或者数组,并且每一维都有其具体的含义。对于这道题目来说则需要声明四维dp列表或数组,首先第一维表示当前是第几个数字(题目中有n个整数那么可以声明n + 1维),第二维可以表示当前数字是几,这样可以通过当前的数字与上一个数字的大小关系判断当前是递减序列的第几个数字,因为每一个数字都是在0-40所以我们可以声明长度为41,第三维表示当前的数字在序列中是第几个递减的数字(长度为3),第四维表示当前序列的总和,这样可以检查满足题目要求的序列和。dp[i][j][k][l]表示第i个数字数值为j并且是递减序列中的第k个数字中总和为l的方案总数。明确dp列表的含义之后就可以使用for循环进行递推了。

③ 首先是需要初始化dp列表的值,判断第一个元素值是否是-1如果是-1那么表示当前的数字可以变为0-40中的任意数字,所以使用循环进行初始化对应的dp值,首先第一层循环表示到当前的第几个数字,判断当前的数字是否是-1,如果是-1说明当前的数字可以变为0-40中的任意一个数字,第二层循环表示当前的数字为j,第三层循环表示上一个数字为jl,第四层循环枚举上一次可能的序列和,判断当前的数字是否大于等于上一个数字进行状态的转移,如果大于等于上一个数字说明当前的数字是第一个递减的序列,也即对应四维dp列表中的第三维为1的dp值,也即当前的数字大于等于上一个数字那么dp值就可以通过加上一个数字对应的序列和为k的并且为i - 1为序列中第一个或第二个递减的数字对应的dp值,如果当前的数字小于上一个数字那么就只能够加上上一个数字对应的序列和为k的并且i - 1为序列中的第一个递减的数字(不能够连续三个递减的数字)的dp值,具体结合具体的代码会更好理解一点。

3. 代码如下:

博主的c++代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=45,M=1605,L=3;
const ll mod=1e9+7;
ll a[N];
ll dp[N][N][L][M];
int main()
{
    ll n;
    scanf("%lld",&n);
    for(ll i=1;i<=n;i++)
    {
        scanf("%lld",&a[i]);
    }
    if(a[1]==-1)
    {
       for(ll i=0;i<=40;i++) dp[1][i][1][i]=1;
    }
    else dp[1][a[1]][1][a[1]]=1;
    for(ll i=2;i<=n;i++)//到了第几个.
    {
        if(a[i]==-1)//不确定.
        {
            for(ll j=0;j<=40;j++)//枚举当前哪个数.
            {
                for(ll lj=0;lj<=40;lj++)//枚举上个数是哪个数.
                {
                    for(ll k=j*(i-1);k<=1600-j;k++)//枚举可行的sum
                    {
                        if(j>=lj)//假设我当前值大于上一个的值.
                        {
                            dp[i][j][1][k+j]=(dp[i][j][1][k+j]+dp[i-1][lj][1][k]+dp[i-1][lj][2][k])%mod;
                        }
                        else
                        {
                            dp[i][j][2][k+j]=(dp[i][j][2][k+j]+dp[i-1][lj][1][k])%mod;
                        }
                    }
                }
            }
        }
        else//确定.
        {
            for(ll lj=0;lj<=40;lj++)//枚举上个数是哪个数.
                {
                    for(ll k=a[i]*(i-1);k<=1600-a[i];k++)//枚举可行的sum
                    {
                        if(a[i]>=lj)//假设我当前值大于上一个的值.
                        {
                            dp[i][a[i]][1][k+a[i]]=(dp[i][a[i]][1][k+a[i]]+dp[i-1][lj][1][k]+dp[i-1][lj][2][k])%mod;
                        }
                        else
                        {
                            dp[i][a[i]][2][k+a[i]]=(dp[i][a[i]][2][k+a[i]]+dp[i-1][lj][1][k])%mod;
                        }
                    }
                }
        }
    }
    ll ans=0;
    for(ll j=0;j<=1600;j++)
    {
        for(ll i=0;i<=40;i++)
        {
            ans=(ans+dp[n][i][1][j])%mod;
            ans=(ans+dp[n][i][2][j])%mod;
        }
    }
    printf("%lld\n",ans);
    return 0;
}

python代码:

if __name__ == '__main__':
    n = int(input())
    nums = list(map(int, input().split()))
    nums.insert(0, 0)
    dp = [[[[0] * 1605 for i in range(3)] for j in range(45)] for k in range(n + 1)]
    mod = 10 ** 9 + 7
    # 初始化第一行
    if nums[1] == -1:
        # 第一个数字可以尝试0-40的数字
        for i in range(41):
            dp[1][i][1][i] = 1
    else:
        dp[1][nums[1]][1][nums[1]] = 1
    for i in range(2, n + 1): # 第i个数字
        if nums[i] == -1:
            # 当前的数字为-1那么可以尝试0~40的数字
            for j in range(41): # j表示当前的数字
                for jl in range(41): # 上一个数字为jl
                    for k in range(j * (i - 1), 1601 - j): # 枚举上一次可能的序列和
                        if j >= jl:
                            dp[i][j][1][k + j] = (dp[i][j][1][k + j] + dp[i - 1][jl][1][k] + dp[i - 1][jl][2][k]) % mod
                        else:
                            dp[i][j][2][k + j] = (dp[i][j][2][k + j] + dp[i - 1][jl][1][k]) % mod
        else:
            # 当前的数字不是-1与当前的数字为-1是类似的只是这里当前的数字是确定的少了一层循环
            for jl in range(41):
                for k in range(nums[i] * (i - 1), 1601 - nums[i]):
                    if nums[i] >= jl:
                        dp[i][nums[i]][1][k + nums[i]] = (dp[i][nums[i]][1][k + nums[i]] + dp[i - 1][jl][1][k] + dp[i - 1][jl][2][k]) % mod
                    else:
                        dp[i][nums[i]][2][k + nums[i]] = dp[i][nums[i]][2][k + nums[i]] + dp[i - 1][jl][1][k] % mod
    res = 0
    for i in range(1601):
        for j in range(41):
            # 累加和为i的dp值
            res = (res + dp[n][j][1][i]) % mod
            res = (res + dp[n][j][2][i]) % mod
    print(res)

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值