动态规划-美丽序列

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

题目描述

牛牛喜欢整数序列,他认为一个序列美丽的定义是
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

思路:类似牛牛和数组那一题。此题添加了另外两个限制。因此开了四维数组。
#include <cstdio>
#include <cstdlib>
#include <climits>
#include <fstream>
#include <algorithm>
#include <cmath>
#include <deque>
#include <vector>
#include <queue>
#include <string>
#include <cstring>
#include <map>
#include <stack>
#include <set>
#include <sstream>
#include <iostream>
using namespace std;

typedef long long ll;
const int mod = 1e9+7;
const double PI=3.14159265358;
const int INF=0x3f3f3f3f;

int n;
//储存输入的数组
ll a[42];
//dp开四维数组:dp[i][j][k][h];
//分别对应第i层,值为j,是递减序列中的第k个,且i层数组的前i个数的和是h;
//把整数序列当成一个数组。所以i表示数组的长度
//因为数组中每个数的范围是0~40,所以j表示0~40.
//因为要求没有三个连续的递减的数。所以用k表示这个数在数组中是三个递减数的第一个。还是第二个
//因为每个说都小于之前的数的平均数。所以用h表示在数组中从第一个直到第i个数的总和。
ll dp[41][41][3][1610];
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);

    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
    }


    memset(dp,0,sizeof(dp));
    //判断第一个数,由于是第一个数,所以他只能是递减序列的第一个
    if(a[1]==-1){
        //由于是-1所以,第一个数可以是0~40中的任一一个数
        for(int i=0;i<=40;i++){
            //第1层值为i且是递减序列中第一个的且总和为i的可能性为1;
            dp[1][i][1][i]=1;
        }
    }else{
        //第1层值为a[i]且是递减序列中第一个的且总和为a[i]的可能性为1;
        dp[1][a[1]][1][a[1]]=1;
    }

    //从第二层往后推
    for(int i=2;i<=n;i++)
    {
        if(a[i]==-1){
            //第i层的数可以是0~40.用j表示
            for(int j=0;j<=40;j++){
                //循环第i-1层的值是0~40.用l表示
                for(int l=0;l<=40;l++){
                    //k表示到目前这一层的总和的值。
                    //由于小于等于前i-1层的数的总和的平均数。
                    //所以用j乘以i-1层表示k的最小值。
                    //最大值是40*40减去当前这一层的值。
                    for(int k=j*(i-1);k<=1600-j;k++)
                    {
                        //如果j>=l:表示这一层的值可以作为递减序列的第一个
                        if(j>=l){
                            //用第i层值为j的。且位于递减序列的第一个的。且总和为k+j的dp
                            //加上上一层的值为0~40.且位于递减序列的第一个和第二个的。且总和为k
                            //的dp相加。
                            dp[i][j][1][k+j]=(dp[i][j][1][k+j]+dp[i-1][l][1][k])%mod;
                            dp[i][j][1][k+j]=(dp[i][j][1][k+j]+dp[i-1][l][2][k])%mod;
                        }else{
                            //如果j<l。表示这个数只能是递减序列的第二个。
                            //第i层值为j的。且位于递减序列的第二个的。且总和为k+j的dp
                            //加上上一层的值为0~40.且位于递减序列的第一个。且总和为k的dp。
                            dp[i][j][2][k+j]=(dp[i][j][2][k+j]+dp[i-1][l][1][k])%mod;
                        }
                    }
                }
            }
        }else{
            //类似上面的情况。不过是把j确定为a[i]了
            for(int l=0;l<=40;l++){
                for(int k=a[i]*(i-1);k<=1600-a[i];k++){
                    if(a[i]>=l){
                        dp[i][a[i]][1][k+a[i]]=(dp[i][a[i]][1][k+a[i]]+dp[i-1][l][1][k])%mod;
                        dp[i][a[i]][1][k+a[i]]=(dp[i][a[i]][1][k+a[i]]+dp[i-1][l][2][k])%mod;
                    }else{
                        dp[i][a[i]][2][k+a[i]]=(dp[i][a[i]][2][k+a[i]]+dp[i-1][l][1][k])%mod;
                    }
                }
            }
        }
    }

    ll ans=0;
    //循环当前n层的值为0~40的所有可能情况。
    for(int i=0;i<=40;i++){
        //循环所有可能和的情况。将k设置=i*n。是为了简便计算
        for(int k=i*n;k<=1600;k++){
            //需要累加位于递减序列的第一个和第二个的两种情况的dp的和。
            ans=(ans+dp[n][i][1][k])%mod;
            ans=(ans+dp[n][i][2][k])%mod;
        }
    }
    cout<<ans<<endl;

    return 0;
}
原文地址:https://www.cnblogs.com/mzchuan/p/13589831.html