【题解】AcWing 271.杨老师的照相排列

AcWing 271.杨老师的照相排列

题目描述

N N N 个学生合影,站成左端对齐的 k k k 排,每排分别有 N 1 , N 2 , … , N k N1,N2,…,N_k N1,N2,,Nk 个人。 ( N 1 ≥ N 2 ≥ … ≥ N k N1≥N2≥…≥N_k N1N2Nk)

1 1 1 排站在最后边,第 k k k 排站在最前边。

学生的身高互不相同,把他们从高到底依次标记为 1 , 2 , … , N 1,2,…,N 1,2,,N

在合影时要求每一排从左到右身高递减,每一列从后到前身高也递减。

问一共有多少种安排合影位置的方案?

下面的一排三角矩阵给出了当 N = 6 , k = 3 , N 1 = 3 , N 2 = 2 , N 3 = 1 N=6,k=3,N_1=3,N_2=2,N_3=1 N=6,k=3,N1=3,N2=2,N3=1 时的全部 16 16 16 种合影方案。注意身高最高的是 1 1 1,最低的是 6 6 6

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 表示总排数。

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

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

输出格式

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

每个答案占一行。

数据范围

1 ≤ k ≤ 5 1≤k≤5 1k5,学生总人数不超过 30 30 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

题目分析

状态表示:因为排数最多为 5 5 5 排,所以可以用五维数组 f [ a , b , c , d , e ] f[a,b,c,d,e] f[a,b,c,d,e] 表示从后往前每一排分别站了 a , b , c , d , e a,b,c,d,e a,b,c,d,e 个人的方案总数,若排数 k < 5 k<5 k<5,则可以增加人数为 0 0 0 的排。

状态计算:每当安排一名新学生时,按照排数从后到前考虑 (注意 1 1 1 是最高的,新安排的是最矮的,数组的第一维是最后一排,新安排的一定站在最右边)。

  1. 最后一排人数少于规定人数,那么新学生可以放在最后一排, f [ a + 1 , b , c , d , e ] + = f [ a , b , c , d , e ] f[a+1,b,c,d,e]+=f[a,b,c,d,e] f[a+1,b,c,d,e]+=f[a,b,c,d,e]
  2. 倒数第二排人数少于规定人数,且少于倒数第一排 (上一排) 人数 ( b < a b<a b<a),那么新学生可以放在倒数第二排, f [ a , b + 1 , c , d , e ] + = f [ a , b , c , d , e ] f[a,b+1,c,d,e]+=f[a,b,c,d,e] f[a,b+1,c,d,e]+=f[a,b,c,d,e]
  3. ……
  4. ……
  5. ……

状态初始化: f [ 0 , 0 , 0 , 0 , 0 ] = 1 f[0,0,0,0,0]=1 f[0,0,0,0,0]=1

从本题的解法中可以发现,设计状态的转移方程时,不一定要以“如何计算出一个状态”的形式给出,也可以考虑 “一个状态应该更新哪些后续阶段的未知状态”。在有的题目中,其中一种可能会比另一种思考起来更加自然、简便。

代码 (如何计算出一个状态):

#include <iostream>
#include <cstring>
#define ll long long
using namespace std;

int k, n[6];
ll f[32][32][32][32][32];  //注意这里数组不要太大,否则memset会超时

int main(){
    
    
    while(cin >> k && k){
    
    
        memset(n, 0, sizeof(n)), memset(f, 0, sizeof(f));
        f[0][0][0][0][0] = 1;
        
        for (int i = 1; i <= k; i ++)
            cin >> n[i];
            
        for (int a = 0; a <= n[1]; a ++)
            for (int b = 0; b <= min(a, n[2]); b ++)
                for (int c = 0; c <= min(b, n[3]); c ++)
                    for (int d = 0; d <= min(c, n[4]); d ++)
                        for (int e = 0; e <= min(d, n[5]); e ++){
    
    
                            ll &v = f[a][b][c][d][e];
                            if (a && a > b) v += f[a - 1][b][c][d][e];  //严格意义上括号里的第二个条件是需要的,
                            if (b && b > c) v += f[a][b - 1][c][d][e];  //但是不写也不影响结果,
                            if (c && c > d) v += f[a][b][c - 1][d][e];  //因为不合法的状态为0,
                            if (d && d > e) v += f[a][b][c][d - 1][e];  //加上也无妨
                            if (e) v += f[a][b][c][d][e - 1];
                        }
        cout << f[n[1]][n[2]][n[3]][n[4]][n[5]] << endl;
    }
    return 0;
}

当然,按照蓝书的“一个状态应该更新哪些后续阶段的未知状态”,上述循环也可改写成如下:
代码 (一个状态应该更新哪些后续阶段的未知状态):

for (int a = 0; a <= n[1]; a ++)
	for (int b = 0; b <= n[2]; b ++)
		for (int c = 0; c <= n[3]; c ++)
			for (int d = 0; d <= n[4]; d ++)
				for (int e = 0; e <= n[5]; e ++){
    
    
					ll v = f[a][b][c][d][e];
					if (a < n[1]) f[a + 1][b][c][d][e] += v;
					if (b < n[2] && a > b) f[a][b + 1][c][d][e] += v;
					if (c < n[3] && b > c) f[a][b][c + 1][d][e] += v;
					if (d < n[4] && c > d) f[a][b][c][d + 1][e] += v;
					if (e < n[5] && d > e) f[a][b][c][d][e + 1] += v;
				}

猜你喜欢

转载自blog.csdn.net/f4u4u4r/article/details/121258634