【算法竞赛进阶指南】POJ 2279 - 杨老师的照相排列 - 线性dp计算方案数

题目描述 

传送门:Acwing / POJ

(注意在poj提交时换掉代码中的万能头

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

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

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

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

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

下面的一排三角矩阵给出了当 N=6,k=3,N1=3,N2=2,N3=1 时的全部16种合影方案。注意身高最高的是1,最低的是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=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
难度:简单
时/空限制:2s / 512MB
来源:《算法竞赛进阶指南》

思路

  1. 首先我们可以看到k小于等于5,对于每个人我们需要决定这个人放在哪个位置
  2. 如果从高到低依次安排每个同学位置,那么在安排过程中,当前同学一定占据某排最靠左的某个位置,并且安排后从后往前每排人数单调递减
  3. 考虑到k小于等于5,我们可以考虑用每排的人数去表示当前排列形式的方案数,即用f[a][b][c][d][e]表示第一排人数为a,第二排人数为b。。。的方案数。
  4. 对于f[a][b][c][d][e]的排列形式,是由最后一个人安排在第一排,第二排或第三排等等决策得来的,那么可以由下面的代码进行计算。
  5. ll &x = f[a][b][c][d][e];
    if (a && a - 1 >= b) x += f[a - 1][b][c][d][e];
    if (b && b - 1 >= c) x += f[a][b - 1][c][d][e];
    if (c && c - 1 >= d) x += f[a][b][c - 1][d][e];
    if (d && d - 1 >= e) x += f[a][b][c][d - 1][e];
    if (e) x += f[a][b][c][d][e - 1];
  6. 注意前一排人数要小于后一排,数组太大,建议在main函数里根据实际需要定义数组或vector大小,或者用for循环根据需要初始化,这会大大加快程序速度(1175ms -> 2ms)

代码

#include 
using namespace std;
#define pb push_back
#define mp make_pair
#define fi first
#define se second
typedef long long ll;
typedef unsigned long long ull;
typedef pair PII;
typedef pair pll;
const int mod = 1e9 + 7;
const int N = 31;
const int INF = 0x3f3f3f3f;
ll qpow(ll base, ll n){ll ans = 1; while (n){if (n & 1) ans = ans * base % mod; base = base * base % mod; n >>= 1;} return ans;}
ll gcd(ll a, ll b){return b ? gcd(b, a % b) : a;}

int main()
{
	int n;
	while (cin >> n && n){
		int num[6] = {};
		for (int i = 1; i <= n; ++ i){
			cin >> num[i];
		}
		ll f[num[1] + 1][num[2] + 1][num[3] + 1][num[4] + 1][num[5] + 1];
		memset(f, 0, sizeof(f));
		//fill_n(f[0][0][0][0], (num[1] + 1) * (num[2] + 1) * (num[3] + 1) * (num[4] + 1) * (num[5] + 1), 0);
		f[0][0][0][0][0] = 1;
		for (int a = 0; a <= num[1]; ++ a){
			for (int b = 0; b <= num[2]; ++ b){
				for (int c = 0; c <= num[3]; ++ c){
					for (int d = 0; d <= num[4]; ++ d){
						for (int e = 0; e <= num[5]; ++ e){
							ll &x = f[a][b][c][d][e];
							if (a && a - 1 >= b) x += f[a - 1][b][c][d][e];
							if (b && b - 1 >= c) x += f[a][b - 1][c][d][e];
							if (c && c - 1 >= d) x += f[a][b][c - 1][d][e];
							if (d && d - 1 >= e) x += f[a][b][c][d - 1][e];
							if (e) x += f[a][b][c][d][e - 1];

						}
					}
				}
			}
		}
		cout << f[num[1]][num[2]][num[3]][num[4]][num[5]] << '\n';
	}
	return 0;
}

 

你可能感兴趣的:(动态规划,c++,算法,动态规划,acm竞赛)