271. 杨老师的照相排列 (线性DP)

271. 杨老师的照相排列

杨老师希望给他的班级拍一张合照。

学生们将站成左端对齐的多排,靠后的排站的人数不能少于靠前的排。

例如,12名学生(从后向前)可以排列成每排5,3,3,1人,如下所示:

X X X X X
X X X
X X X
X
同时,杨老师希望同行学生身高从左到右依次降低,同列学生身高从后向前依次降低。

还以12名学生为例,给学生们编号(号码越小代表身高越高)后,按照此规则可进行如下两种安排:

1 2 3 4 5 1 5 8 11 12
6 7 8 2 6 9
9 10 11 3 7 10
12 4
杨老师希望知道给定每排的人数,在满足规则的情况下,一共能有多少种位置安排。

例如,规定一共三排,每排3,2,1人,则共有16种安排方法如下:

123 123 124 124 125 125 126 126 134 134 135 135 136 136 145 146
45 46 35 36 34 36 34 35 25 26 24 26 24 25 26 25
6 5 6 5 6 4 5 4 6 5 6 4 5 4 3 3
现在请你编写一个程序,确定在给定每排人数的情况下,不同安排的数量。

输入格式
输入包含多组测试数据。

每组数据两行,第一行包含一个整数k表示总排数。

第二行包含k个整数,表示从后向前每排的具体人数。

当输入k=0的数据时,表示输入终止,且该数据无需处理。

输出格式
每组测试数据输出一个答案,表示不同安排的数量。

每个答案占一行。

数据范围
1≤k≤5,学生总人数不超过30人。

输入样例:
1
30
5
1 1 1 1 1
3
3 2 1
4
5 3 3 1
5
6 5 4 3 2
2
15 15
0
输出样例:
1
1
16
4158
141892608
9694845

(这一题卡了好几个星期,一直找不到转移的条件与方程,看了书上的题解也是懵懵懂懂的,今天终于算了稍微的理解了一点。)

题目分析:

从题目中我们可以知道每一列、每一行的身高是单调的,同时观察到这题的数据,k[1, 5],并且每行不超过30,很容易想到该题可以用dp去写。在这里,我们可以考虑用一个 k 元组(a1, a2, a3 … ak)来表示已经安排的学生的数量。我们可以通过前一个状态逐一安排插入学生来转移到下一个状态。而最终的转移目标是 dp [ a [ 0 ], a [ 1 ], a [ 2 ], a [ 3 ] ,a [ 4 ] ] 同时,在转移的时候我们需要满足下面两个条件:

1. ai < Ni;
2. i = 1 或 ai-1 > ai

我们可以得到转移方程为:

if(a1 < N1)dp [ a1 + 1, a2, a2, a4, a5 ] += dp [ a1, a2, a2, a4, a5 ];
if(a2 < N2 && a2 < a1)dp [ a1, a2 + 1, a2, a4, a5 ] += dp [ a1, a2, a2, a4, a5 ] ;
…以此类推

只要学生安排好之后,我们可以不用再考虑。
这里我们的dp [ a1, a2, a2, a4, a5 ] 表示各排从左端起分别站了 a1, a2, a3 ,a4, a5 个人时合影的方案数量。
注意 : 我们要初始化dp[0, 0, 0, 0, 0] = 1; 因为dp[0, 0, 0, 0, 0] 也是一个状态

代码:

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <map>
#include <stack>
#define maxn 2005
#define INF 0x3f3f3f
using namespace std;

typedef long long ll;

int k,a[40];


int main()
{
    while(~scanf("%d",&k),k)
    {
        memset(a,0,sizeof(a));
        for(int i = 0; i < k; i ++)
            cin >> a[i];
        ll dp[a[0] + 1][a[1] + 1][a[2] + 1][a[3] + 1][a[4] + 1];
        memset(dp,0,sizeof(dp));
        dp[0][0][0][0][0] = 1;

        for(int i = 0; i <= a[0]; i ++)
            for(int j = 0; j <= a[1]; j ++)
                for(int n = 0; n <= a[2]; n ++)
                    for(int m = 0; m <= a[3]; m ++)
                        for(int l = 0; l <= a[4]; l ++)
                        {
                            if(i < a[0])
                                dp[i + 1][j][n][m][l] += dp[i][j][n][m][l];
                            if(j < a[1] && j < i)
                                dp[i][j + 1][n][m][l] += dp[i][j][n][m][l];
                            if(n < a[2] && n < j)
                                dp[i][j][n + 1][m][l] += dp[i][j][n][m][l];
                            if(m < a[3] && m < n)
                                dp[i][j][n][m + 1][l] += dp[i][j][n][m][l];
                            if(l < a[4] && l < m)
                                dp[i][j][n][m][l + 1] += dp[i][j][n][m][l];
                        }
        cout << dp[a[0]][a[1]][a[2]][a[3]][a[4]] << endl;
    }
    return 0;
}

这里提交了一次超内存了,因为5维数组太大,因此把dp数组的定义放在了输入之后。
还有一次由于没有初始化数组而WA了。
该题还有别的解法,下次学到回来补。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值