我们可以发现性质,
1.从左到右身高递减,从上到下身高也是递减,同时编号越大的同学身高越高
2.每排人数N1>=N2....
分析如下
以此类推,其实我们可以发现一个规律
1.对于任意一排,我们都是从他最左边依次安排排列(不存在隔空安排)
2.对于任意一列,他当前被安排的长度都是>=后面所有列被安排长度
也就是说:行,列的安排,都必须是线性的,存在递推关系,前面安排了,后面才可以安排
我们就可以得到如下的规律
任意一个排列的轮廓都是1,2这两种轮廓
所以我们可以得出,因为安排是线性的,有两种规律
1.必须同一行前面[1,i-1]都安排了,才能安排[i]
2.当前行已安排的数量不能大于前面任何一行的数量
有两种dp的分析方法
1.填表法(当前排列的子集合)
根据当前排列可能的子集合进行划分,分析当前排列所有可能的来源
这是属于逆拓扑顺序,从出度为0的点开始思考怎么划分,从入度为0的点卡斯和计算
状态表示:f[a,b,c,d,e]
集合:所有第一排有a个人,第二个排有b个人…第五排有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]
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N=31;
int kk[N];
typedef long long ll;
ll dp[N][N][N][N][N];
int main()
{
int t;
while(cin>>t,t)
{
int k; //安排几列
k=t;
memset(kk,0,sizeof kk);
memset(dp,0,sizeof dp);
for(int i=0;i<k;i++)
cin>>kk[i]; //每列人数
//初始边界
dp[0][0][0][0][0]=1; //第一行0人...的方案数量是1
for(int a=0;a<=kk[0];a++)
for(int b=0;b<=min(a,kk[1]);b++) //不能大于前面的任何一行
for(int c=0;c<=min(b,kk[2]);c++)
for(int d=0;d<=min(c,kk[3]);d++)
for(int e=0;e<=min(e,kk[4]);e++)//kk[4]==0,保证至少被执行一次
{
ll &x=dp[a][b][c][d][e];
if(a&&a-1>=b) //插入前a长度应该>=b
x+=dp[a-1][b][c][d][e];
if(b&&b-1>=c)
x+=dp[a][b-1][c][d][e];
if(c&&c-1>=d)
x+=dp[a][b][c-1][d][e];
if(d&&d-1>=e)
x+=dp[a][b][c][d-1][e];
if(e)
x+=dp[a][b][c][d][e-1];
}
cout<<dp[kk[0]][kk[1]][kk[2]][kk[3]][kk[4]]<<'\n';
}
return 0;
}
2.当前排列的递推结果
根据当前排列递推后所有可能的结果作为我们的划分依据
两种思考方式最后得到的状态计算图都是一样的
这是属于正拓扑顺序,从入度为0的点开始思考怎么推导,从而开始计算
状态表示:f[a,b,c,d,e]
集合:所有第一排有a个人,第二个排有b个人…第五排有e个人的方案
属性:总方案数
状态计算:
从头向尾考虑,考虑我们如何安排第一个人,这个人如何推导到下一个父集合
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N=31;
int kk[6];
typedef long long ll;
ll dp[N][N][N][N][N];
int main()
{
int k;
while(cin>>k,k)
{
memset(dp,0,sizeof dp);
memset(kk,0,sizeof kk);
for(int i=0;i<k;i++)
cin>>kk[i];
dp[0][0][0][0][0]=1;
for(int a=0;a<=kk[0];a++)
for(int b=0;b<=min(a,kk[1]);b++)
for(int c=0;c<=min(b,kk[2]);c++)
for(int d=0;d<=min(c,kk[3]);d++)
for(int e=0;e<=min(d,kk[4]);e++)
{
ll &x=dp[a][b][c][d][e];
if(a<kk[0])
dp[a+1][b][c][d][e]+=x;
if(b<kk[1])
dp[a][b+1][c][d][e]+=x;
if(c<kk[2])
dp[a][b][c+1][d][e]+=x;
if(d<kk[3])
dp[a][b][c][d+1][e]+=x;
if(e<kk[4])
dp[a][b][c][d][e+1]+=x;
}
cout<<dp[kk[0]][kk[1]][kk[2]][kk[3]][kk[4]]<<'\n';
}
}