核心性质:
从高到低依次安排每个同学的位置,那么在安排过程中,当前同学一定占据每排最靠左的连续若干个位置,且从后往前每排人数单调递减。否则一定不满足“每排从左到右身高递减,从后到前身高也递减”这个要求。
DP的核心思想是用集合来表示一类方案,然后从集合的维度来考虑状态之间的递推关系。
受上述性质启发,状态表示为:
f[a][b][c][d][e]代表从后往前每排人数分别为a, b, c, d, e的所有方案的集合, 其中a >= b >= c >= d >= e;
f[a][b][c][d][e]的值是该集合中元素的数量;
状态计算对应集合的划分,令最后一个同学被安排在哪一排作为划分依据,可以将f[a][b][c][d][e]划分成5个不重不漏的子集:
当a > 0 && a - 1 >= b时,最后一个同学可能被安排在第1排,方案数是f[a - 1][b][c][d][e];
当b > 0 && b - 1 >= c时,最后一个同学可能被安排在第2排,方案数是f[a][b - 1][c][d][e];
当c > 0 && c - 1 >= d时,最后一个同学可能被安排在第3排,方案数是f[a][b][c - 1][d][e];
当d > 0 && d - 1 >= e时,最后一个同学可能被安排在第4排,方案数是f[a][b][c][d - 1][e];
当e > 0时,最后一个同学可能被安排在第5排,方案数是f[a][b][c][d][e - 1];
作者:yxc
链接:https://www.acwing.com/solution/content/4954/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long LL;
const int N = 31;
LL f[N][N][N][N][N];//定义一个五维数组 f,用于存储动态规划的状态。每个状态 f[a][b][c][d][e] 表示满足特定条件下的排列组合数量。
int n;
int main()
{
while(scanf("%d",&n),n)
{
int s[5]={0};
for(int i = 0;i<n;i++) scanf("%d",&s[i]);
memset(f,0,sizeof f);
f[0][0][0][0][0]=1;//将初始状态 f[0][0][0][0][0] 设置为 1,表示初始的排列组合数量为 1。
for(int a = 0;a<=s[0];a++)
for(int b = 0;b<=min(a,s[1]);b++)
for(int c = 0;c<=min(b,s[2]);c++)
for(int d = 0;d<=min(c,s[3]);d++)
for(int e = 0;e<=min(d,s[4]);e++)
{
LL&v = f[a][b][c][d][e];//引用当前状态,方便后续操作。
if(a&&a-1>=b) v+=f[a-1][b][c][d][e];
if(b&&b-1>=c) v+=f[a][b-1][c][d][e];
if(c&&c-1>=d) v+=f[a][b][c-1][d][e];
if(d&&d-1>=e) v+=f[a][b][c][d-1][e];
if(e) v+=f[a][b][c][d][e-1];
}
printf("%lld\n",f[s[0]][s[1]][s[2]][s[3]][s[4]]);
}
return 0;
}