大致意思是:
现在有n个人,要排成k行,每行分别是n1,n2,……,nk的人,每个人分别有一个编号,要求每个人的编号要小于他左边和上面的,问有多少种满足题意的方案。
数据范围 n<=30 k<=5
《算法竞赛进阶指南》告诉我们,DP应该是从一个或几个起点出发,从已知的状态空间的边界点上向还没有被计算的状态空间扩展,最后扩展完整个问题的状态空间。
我们应该选取一个不可逆的方向去思考这个过程,如果我们规定编号从小到大依次去考虑,那么,现在每增加一个元素,都是把这个元素选择一行然后放在最右边的过程,所以转移的分类讨论就是建立在具体选取哪一行。
当序确定下来之后,应该考察
就像本题,当一个元素增加进来之后,唯一变化的应该是有某一行的长度变长了,那么应该是需要用每一行的长度去标记状态,这里的一个小的思考的trick是转移的时候要求当前行已经填的元素个数要比上一行少,我推测这里是有一些题目上没有说清楚的地方,应该要保证n1>=n2>=……>=nk,那么这样的话,当本行元素已经跟上一行元素一样多的时候,如果我这里在本行放了,那么上一行之后放的元素一定比我当前行的元素要大了,所以不满足题意的要求,这个思考的trick也是对于看题解的我不太好想到的,
当确定了状态的表示之后,转移的条件什么的比较也都要用状态的某一维度或者与状态的某一维度一一对应的量去表示。或许我觉得我这题被卡住的最大的问题就在于这里,如何用一个量去表示出元素的大小关系,没想到竟然可以用数量关系去表述出大小关系!
#include<cstdio> #include<cstring> using namespace std; typedef unsigned int ui; ui dp[31][16][11][8][7]; int len[6]; int n; int main() { while (scanf("%d",&n)!=EOF&&n) { memset(len,0,sizeof(len)); for (int i=1;i<=n;i++) scanf("%d",&len[i]); memset(dp,0,sizeof(dp)); dp[0][0][0][0][0]=1; for (int i1=0;i1<=len[1];i1++) for (int i2=0;i2<=len[2];i2++) for (int i3=0;i3<=len[3];i3++) for (int i4=0;i4<=len[4];i4++) for (int i5=0;i5<=len[5];i5++) { if (i1<len[1]) dp[i1+1][i2][i3][i4][i5]+=dp[i1][i2][i3][i4][i5]; if (i2<len[2]&&i2<i1) dp[i1][i2+1][i3][i4][i5]+=dp[i1][i2][i3][i4][i5]; if (i3<len[3]&&i3<i2) dp[i1][i2][i3+1][i4][i5]+=dp[i1][i2][i3][i4][i5]; if (i4<len[4]&&i4<i3) dp[i1][i2][i3][i4+1][i5]+=dp[i1][i2][i3][i4][i5]; if (i5<len[5]&&i5<i4) dp[i1][i2][i3][i4][i5+1]+=dp[i1][i2][i3][i4][i5]; } printf("%u ",dp[len[1]][len[2]][len[3]][len[4]][len[5]]); } return 0; }