杨老师的照相排列题解

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

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

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

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

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

下面的一排三角矩阵给出了当 N=6,k=3,N1=3,N2=2,N3=1N=6时的全部 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

本文基于你已经发现:从小到大依次把数字一个个往格子里填,填到任意一个数时,被填的格子会构成一个"满的"、"中间没有缺的"图形。如果不是形成这样一个图形,就没法把缺的那块填上一个数。

我们的目的是求解出把所有格子填满的方案数是多少。

如何求出方案数呢?

假设问题的规模很小:1行1个人,我们只能把1放这唯一的一格,方案数为1。

1行2个人,现在人数增加到了2,我们填好1后,要填2了,2的位置只有1个。

2行,其中第一行2个人,第2行1个人,我们填到2就有了2个可选位置。

有多少个位置可选必然是与方案数有关的。为了求方案数,我们要关注可选位置。

所以,我们可以看看填(N1+...Nk)的时候有几个可选位置。

借助图来描述(头顺时针转90度看。。)

 我们看看其中一个位置,我们要填(N1+...Nk)到这个位置。

可选位置有3个,我们填了其中一个位置其实就确定出了一部分方案的个数,为什么呢?

这部分方案就等于填出"框起来的图像"的方案数,所以我们想求方案数,就需要知道每个子结构的方案数。

所以我们要用一种方式存储每个子结构的方案数,用f[a1,a2,a3,a4,a5]这样一个5维数组来存。

来个5重循环就能求出所有子结构方案数,最终得出答案。

下面给出ac代码

#include<iostream>
#include<cstring>
using namespace std;
long long f[31][31][31][31][31];//我们关心的状态是一个个连续方格块,至于方格块里的数字怎么摆放我们也不关心,我们关心的是这个方格块有多少个方案。
int a[6], ans;
int main()
{
	int k;
	while (cin >> k)
	{
	    memset(f,0,sizeof(f));
	    memset(a,0,sizeof(a));
		if (k == 0) break;
		for (int i = 1;i <= k;i++) cin >> a[i];
		f[0][0][0][0][0] = 1;
		for (int i = 1;i <= a[1];i++)
		{
			for (int j = 0;j <= i && j <= a[2];j++)
			{
				for (int k = 0;k <= j&&k <= a[3];k++)
				{
					for (int l = 0;l <= k && l <= a[4];l++)
					{
						for (int m = 0;m <= l && m <= a[5];m++)
						{
							if (i - 1 >= j) f[i][j][k][l][m] += f[i - 1][j][k][l][m];
							if (j - 1 >= k)  f[i][j][k][l][m] += f[i][j - 1][k][l][m];
							if (k - 1 >= l) f[i][j][k][l][m] += f[i][j][k - 1][l][m];
							if (l - 1 >= m) f[i][j][k][l][m] += f[i][j][k][l - 1][m];
							if(m > 0) f[i][j][k][l][m] += f[i][j][k][l][m - 1];
						}
					}
				}
			}
		}
		cout << f[a[1]][a[2]][a[3]][a[4]][a[5]] << endl;
	}
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
06-01
这道题是一道典型的费用限制最短路题目,可以使用 Dijkstra 算法或者 SPFA 算法来解决。 具体思路如下: 1. 首先,我们需要读入输入数据。输入数据中包含了道路的数量、起点和终点,以及每条道路的起点、终点、长度和限制费用。 2. 接着,我们需要使用邻接表或邻接矩阵来存储图的信息。对于每条道路,我们可以将其起点和终点作为一个有向边的起点和终点,长度作为边权,限制费用作为边权的上界。 3. 然后,我们可以使用 Dijkstra 算法或 SPFA 算法求解从起点到终点的最短路径。在这个过程中,我们需要记录到每个点的最小费用和最小长度,以及更新每条边的最小费用和最小长度。 4. 最后,我们输出从起点到终点的最短路径长度即可。 需要注意的是,在使用 Dijkstra 算法或 SPFA 算法时,需要对每个点的最小费用和最小长度进行松弛操作。具体来说,当我们从一个点 u 经过一条边 (u,v) 到达另一个点 v 时,如果新的费用和长度比原来的小,则需要更新到达 v 的最小费用和最小长度,并将 v 加入到优先队列(Dijkstra 算法)或队列(SPFA 算法)中。 此外,还需要注意处理边权为 0 或负数的情况,以及处理无法到达终点的情况。 代码实现可以参考以下样例代码: ```c++ #include <cstdio> #include <cstring> #include <queue> #include <vector> using namespace std; const int MAXN = 1005, MAXM = 20005, INF = 0x3f3f3f3f; int n, m, s, t, cnt; int head[MAXN], dis[MAXN], vis[MAXN]; struct Edge { int v, w, c, nxt; } e[MAXM]; void addEdge(int u, int v, int w, int c) { e[++cnt].v = v, e[cnt].w = w, e[cnt].c = c, e[cnt].nxt = head[u], head[u] = cnt; } void dijkstra() { priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> q; memset(dis, 0x3f, sizeof(dis)); memset(vis, 0, sizeof(vis)); dis[s] = 0; q.push(make_pair(0, s)); while (!q.empty()) { int u = q.top().second; q.pop(); if (vis[u]) continue; vis[u] = 1; for (int i = head[u]; i != -1; i = e[i].nxt) { int v = e[i].v, w = e[i].w, c = e[i].c; if (dis[u] + w < dis[v] && c >= dis[u] + w) { dis[v] = dis[u] + w; q.push(make_pair(dis[v], v)); } } } } int main() { memset(head, -1, sizeof(head)); scanf("%d %d %d %d", &n, &m, &s, &t); for (int i = 1; i <= m; i++) { int u, v, w, c; scanf("%d %d %d %d", &u, &v, &w, &c); addEdge(u, v, w, c); addEdge(v, u, w, c); } dijkstra(); if (dis[t] == INF) printf("-1\n"); else printf("%d\n", dis[t]); return 0; } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值