杨老师的照相排列

题目链接

https://www.acwing.com/problem/content/273/
http://contest-hunter.org:83/record/116290


题目描述

杨老师希望给他的班级拍一张合照。
学生们将站成左端对齐的多排,靠后的排站的人数不能少于靠前的排
例如,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


思路

  1. 首先看一下范围, ( k < = 5 ) (k<=5) k<=5,所以可以根据每一行维护一个阶段;
  2. 设一个 k k k元组为{k1,k2,k3,k4,k5},那么我们的状态就可以是 f [ i ] [ j ] [ s ] [ l ] [ m ] f[i][j][s][l][m] f[i][j][s][l][m]表示在每一行是那么多人的时候的最多的不同的排列组合数

之后想的就是如何转移,每个决策都与前一个状态相关联着,可以直接根据前一个状态找到当前的状态

  1. 那么你的决策就是:
    f [ i ] [ j ] [ s ] [ l ] [ m ] + = f [ i − 1 ] [ j ] [ s ] [ l ] [ m ] f[i][j][s][l][m]+=f[i-1][j][s][l][m] f[i][j][s][l][m]+=f[i1][j][s][l][m]
    f [ i ] [ j ] [ s ] [ l ] [ m ] + = f [ i ] [ j − 1 ] [ s ] [ l ] [ m ] f[i][j][s][l][m]+=f[i][j-1][s][l][m] f[i][j][s][l][m]+=f[i][j1][s][l][m]
    ……
  2. 之后就是要注意细节:
    1. 设每一行的学生数为ai,那么你枚举(i),(i-1)的时候就不可以超出这个范围内;
    2. 初始状态是 f [ 0 ] [ 0 ] [ 0 ] [ 0 ] [ 0 ] = 1 f[0][0][0][0][0]=1 f[0][0][0][0][0]=1(什么都不选的时候也是一种决策);
    3. 目标状态是 f [ a 1 ] [ a 2 ] [ a 3 ] [ a 4 ] [ a 5 ] f[a1][a2][a3][a4][a5] f[a1][a2][a3][a4][a5]
    4. 注意转移状态的合法性以及排与排之间数量的关系。

DP code

下面有两种不同写法的代码(都是正确的,只是思考的方式不太一样,两次不同的时间写的。)
code1

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;

int k;
int a[6];

void work()
{
	memset(a,0,sizeof(a));
	for(int i=1;i<=k;++i)	cin>>a[i];
	ll f[a[1]+1][a[2]+1][a[3]+1][a[4]+1][a[5]+1];
	memset(f,0,sizeof(f));
	f[0][0][0][0][0]=1;
	
	for(int i=0;i<=a[1];++i)
		for(int j=0;j<=a[2];++j)
			for(int s=0;s<=a[3];++s)
				for(int l=0;l<=a[4];++l)
					for(int m=0;m<=a[5];++m)
					{/*限制条件不要打炸*/
						if(i-1>=0)	f[i][j][s][l][m]+=f[i-1][j][s][l][m];
						if(j-1>=0&&j-1<i)	f[i][j][s][l][m]+=f[i][j-1][s][l][m];
						if(s-1>=0&&s-1<j)	f[i][j][s][l][m]+=f[i][j][s-1][l][m];
						if(l-1>=0&&l-1<s)	f[i][j][s][l][m]+=f[i][j][s][l-1][m];
						if(m-1>=0&&m-1<l)	f[i][j][s][l][m]+=f[i][j][s][l][m-1];
					} 
	printf("%lld\n",f[a[1]][a[2]][a[3]][a[4]][a[5]]);
}

int main()
{
	while(cin>>k&&k)	work(); 
	return 0;
} 

一开始的时候也是打炸了。。。。好垃圾啊。。。。。

错因
因为是要由 i − 1 i-1 i1转移到 i i i.所以转移的数字必须合乎条件,前一行严格少于后一行的行数,此时的限制条件不是 ( j < i ) (j<i) j<i而是 ( j − 1 < i ) (j-1<i) j1<i

补充说明:为什么在限制条件中有j-1<i的条件?

今天 ( 2019 / 09 / 04 ) (2019/09/04) 2019/09/04的时候,我又码了一遍这一道题。但是我在写条件的时候并没有写出 j − 1 < i j-1<i j1<i的条件而是写成了 j − 1 < = i j-1<=i j1<=i,所以就一直在眼查哪里出错了,但是并没有发现自己究竟是哪里出错了,就这样了很长时间,我看了这篇题解——我大概是在两个月之前写的。

但是我依旧并不理解我之前的做法。为什么 j − 1 < i j-1<i j1<i ?题目上不是说在这里插入图片描述这样子的话, j − 1 < = i j-1<=i j1<=i 这样的做法是不是也是正确的,但是为什么答案不对??

之后,我又看了一遍题目和蓝皮书上的题解,我发现是我的思路不对,证明自己的错误性的过程如下:

  • 为什么我会觉得 j − 1 < = i j-1<=i j1<=i是对的?
    是因为题目中说的是不少于,也就是大于等于。
  • 那么这个判定条件用在什么时候?是用来干嘛的?
    条件用在在该排增加一个人的时候,也就是说 f [ i ] [ j ] [ s ] [ l ] [ m ] f[i][j][s][l][m] f[i][j][s][l][m]这个是添加一个人之后的状态,这个时候要保证 j < = i j<=i j<=i,那么若在添加之前便要保证 j − 1 < = i j-1<=i j1<=i的话,就会造成在添加之后 j > i j>i j>i,与题目不符,所以这个判断是错误的。但是,若是判断条件是 j − 1 < i j-1<i j1<i的话,在添加之后保证了 j < = i j<=i j<=i,所以判断条件是 j − 1 < i j-1<i j1<i为了保证添加之后排与排之间的数量关系正确

证毕。

code2

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;

int k;
int a[6];

void work()
{
	memset(a,0,sizeof(a));
	for(int i=1;i<=k;++i)	cin>>a[i];
	ll f[a[1]+1][a[2]+1][a[3]+1][a[4]+1][a[5]+1];
	memset(f,0,sizeof(f));
	f[0][0][0][0][0]=1;/*什么都不选也是一种情况*/
	
	for(int i=0;i<=a[1];++i)
		for(int j=0;j<=a[2];++j)
			for(int s=0;s<=a[3];++s)
				for(int l=0;l<=a[4];++l)
					for(int m=0;m<=a[5];++m)
					{
						if(i<a[1])		f[i+1][j][s][l][m]+=f[i][j][s][l][m];
						if(j<a[2]&&i>j)	f[i][j+1][s][l][m]+=f[i][j][s][l][m];
						if(s<a[3]&&j>s)	f[i][j][s+1][l][m]+=f[i][j][s][l][m];
						if(l<a[4]&&s>l)	f[i][j][s][l+1][m]+=f[i][j][s][l][m];
						if(m<a[5]&&l>m)	f[i][j][s][l][m+1]+=f[i][j][s][l][m];
					}
	printf("%lld\n",f[a[1]][a[2]][a[3]][a[4]][a[5]]);
}

int main()
{
	while(cin>>k&&k)	work();
	return 0;
}

错因
在cin<<a[i]的时候,输入成了cin<<a[k];(这样子的话始终输入的就只有一个数字)【代码的规范真的很重要!】


听说还有一个杨氏矩阵和勾长公式,算了,不会,什么时候有时间再学习。


所谓压力,其实是自身的能力不足;所谓困难,其实是自己的本事不够。

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值