题目描述
牛牛喜欢整数序列,他认为一个序列美丽的定义是
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: 无限制
解题思路
看了好多别人的题解,都是在说动态规划,
主要思路就是说定义一个四维的数组dp[i][last][len][sum],其表示第i个数是last,前i个数组成的序列末尾的递减序列长度为len,前i个数的和为sum,用来存储在这种情况下美丽序列的个数,然后把整数序列从前往后判断,有点类似于子序列问题,根据前面的状况来推出下一步的情况,并且给出状态转移方程:(前缀newnew表示当前状态,不加表示上一个状态)
一开始我也看不懂,但其实可以换一个角度思考问题,既然我用一个数组dp[i][last][len][sum]来表示在当前情况下美丽序列的个数,其中last、len、sum是不确定的,那就直接枚举每一种情况暴力得到结果(我个人认为有时候动态规划和暴力的思想是差不多的,从一种状态转移到另一种状态,状态之间有许多种可能(本题的可能性为-1能变成那些值),则枚举每一种可能)
核心代码
遍历到整数序列的第i位时,判断dp[i][last][len][sum],其中last、len、sum有多种可能性,直接用for循环嵌套暴力枚举每一种可能性,for循环之间的顺序不重要,只是对last、len、sum的所有可能性进行暴力枚举而已,顺序变化不会影响最终结果(前缀newnew表示当前状态,不加表示上一个状态)
#include<bits/stdc++.h>
#define maxn 45
#define mod 1000000007
#define PE(x,y) x=(x+y)%mod
using namespace std;
int a[maxn],n;//存储整数序列
long long dp[maxn][maxn][3][1605];
//dp[i][last][len][sum]表示第i个数是last,前i个数组成的序列末尾的递减序列长度为len,前i个数的和为sum
//用来表示可能的情况
for(int i=2;i<=n;i++){
//分-1和不是-1的情况讨论
if(a[i]!=-1){//确定性
for(int last=0;last<=40;last++){//前一位的可能性
for(int sum=a[i]*(i-1);sum<=40*i-a[i];sum++){//由定义2可以推出前面所有数的和的范围
int newsum=sum+a[i];
if(a[i]<last){
PE(dp[i][a[i]][2][newsum],dp[i-1][last][1][sum]);}
else{
PE(dp[i][a[i]][1][newsum],dp[i-1][last][2][sum]);
PE(dp[i][a[i]][1][newsum],dp[i-1][last][1][sum]);}
}
}
continue;//代替了else的作用
}
for(int newlast=0;newlast<=40;newlast++){//不确定性
for(int last=0;last<=40;last++){
for(int sum=newlast*(i-1);sum<=40*i-a[i];sum++){//由定义2可以推出前面所有数的和的范围
int newsum=sum+newlast;
if(newlast<last){
PE(dp[i][newlast][2][newsum],dp[i-1][last][1][sum]);}
else{
PE(dp[i][newlast][1][newsum],dp[i-1][last][2][sum]);
PE(dp[i][newlast][1][newsum],dp[i-1][last][1][sum]);}
}
}
}
}
先确定好当前第i位相关last、len、sum的值,然后判断前一位的所有可能的情况中有多少种满足条件,满足就加1,然后再变换第i位相关last、len、sum的值,重复上一步
题解代码
#include<bits/stdc++.h>
#define maxn 45
#define mod 1000000007
#define PE(x,y) x=(x+y)%mod
using namespace std;
int a[maxn],n;//存储整数序列
long long dp[maxn][maxn][3][1605];
//dp[i][last][len][sum]表示第i个数是last,前i个数组成的序列末尾的递减序列长度为len,前i个数的和为sum
//用来表示可能的情况
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];}
if(a[1]!=-1)dp[1][a[1]][1][a[1]]=1;
else{
for(int last=0;last<=40;last++){
dp[1][last][1][last]=1;}
}
for(int i=2;i<=n;i++){
//分-1和不是-1的情况讨论
if(a[i]!=-1){//确定性
for(int last=0;last<=40;last++){//前一位的可能性
for(int sum=a[i]*(i-1);sum<=40*i-a[i];sum++){//由定义2可以推出前面所有数的和的范围
int newsum=sum+a[i];
if(a[i]<last){
PE(dp[i][a[i]][2][newsum],dp[i-1][last][1][sum]);}
else{
PE(dp[i][a[i]][1][newsum],dp[i-1][last][2][sum]);
PE(dp[i][a[i]][1][newsum],dp[i-1][last][1][sum]);}
}
}
continue;//代替了else的作用
}
for(int newlast=0;newlast<=40;newlast++){//不确定性
for(int last=0;last<=40;last++){
for(int sum=newlast*(i-1);sum<=40*i-a[i];sum++){//由定义2可以推出前面所有数的和的范围
int newsum=sum+newlast;
if(newlast<last){
PE(dp[i][newlast][2][newsum],dp[i-1][last][1][sum]);}
else{
PE(dp[i][newlast][1][newsum],dp[i-1][last][2][sum]);
PE(dp[i][newlast][1][newsum],dp[i-1][last][1][sum]);}
}
}
}
}
long long ans=0;
//三个for循环的顺序无关,没有影响
for(int len=1;len<=2;len++){
for(int last=0;last<=40;last++){
for(int sum=0;sum<=1600;sum++){
PE(ans,dp[n][last][len][sum]);}
}
}
cout<<ans<<endl;
return 0;
}