271. 杨老师的照相排列(填表法,线性dp)

我们可以发现性质,

1.从左到右身高递减,从上到下身高也是递减,同时编号越大的同学身高越高

2.每排人数N1>=N2....

分析如下

 

 以此类推,其实我们可以发现一个规律

1.对于任意一排,我们都是从他最左边依次安排排列(不存在隔空安排)

2.对于任意一列,他当前被安排的长度都是>=后面所有列被安排长度

也就是说:行,列的安排,都必须是线性的,存在递推关系,前面安排了,后面才可以安排

我们就可以得到如下的规律

 任意一个排列的轮廓都是1,2这两种轮廓

所以我们可以得出,因为安排是线性的,有两种规律

1.必须同一行前面[1,i-1]都安排了,才能安排[i]

2.当前行已安排的数量不能大于前面任何一行的数量

有两种dp的分析方法

1.填表法(当前排列的子集合)

根据当前排列可能的子集合进行划分,分析当前排列所有可能的来源

这是属于逆拓扑顺序,从出度为0的点开始思考怎么划分,从入度为0的点卡斯和计算
状态表示:f[a,b,c,d,e]
集合:所有第一排有a个人,第二个排有b个人…第五排有e个人的方案
属性:总方案数
状态计算
从尾向头考虑,考虑我们如何安排最后一个学生。

状态计算对应集合的划分,令最后一个同学被安排在哪一排作为划分依据,可以将f[a][b][c][d][e]划分成5个不重不漏的子集:

  • 当a > 0 && a - 1 >= b时,最后一个同学可能被安排在第1排,方案数是f[a - 1][b][c][d][e];
  • 当b > 0 && b - 1 >= c时,最后一个同学可能被安排在第2排,方案数是f[a][b - 1][c][d][e];
  • 当c > 0 && c - 1 >= d时,最后一个同学可能被安排在第3排,方案数是f[a][b][c - 1][d][e];
  • 当d > 0 && d - 1 >= e时,最后一个同学可能被安排在第4排,方案数是f[a][b][c][d - 1][e];
  • 当e > 0时,最后一个同学可能被安排在第5排,方案数是f[a][b][c][d][e - 1]
​
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N=31;
int kk[N];
typedef long long ll;
ll dp[N][N][N][N][N];

int main()
{
    int t;
    while(cin>>t,t)
    {
        int k;  //安排几列
        k=t;
        memset(kk,0,sizeof kk);
        memset(dp,0,sizeof dp);
        for(int i=0;i<k;i++)
            cin>>kk[i]; //每列人数
        //初始边界
        dp[0][0][0][0][0]=1;    //第一行0人...的方案数量是1
        for(int a=0;a<=kk[0];a++)
            for(int b=0;b<=min(a,kk[1]);b++) //不能大于前面的任何一行
                for(int c=0;c<=min(b,kk[2]);c++)
                    for(int d=0;d<=min(c,kk[3]);d++)
                        for(int e=0;e<=min(e,kk[4]);e++)//kk[4]==0,保证至少被执行一次
                        {
                            ll &x=dp[a][b][c][d][e];            
                            if(a&&a-1>=b)   //插入前a长度应该>=b
                                x+=dp[a-1][b][c][d][e];
                            if(b&&b-1>=c)
                                x+=dp[a][b-1][c][d][e];
                            if(c&&c-1>=d)
                                x+=dp[a][b][c-1][d][e];
                            if(d&&d-1>=e)
                                x+=dp[a][b][c][d-1][e];
                            if(e)
                                x+=dp[a][b][c][d][e-1];
                        }
        cout<<dp[kk[0]][kk[1]][kk[2]][kk[3]][kk[4]]<<'\n';
    }
    return 0;
}

​

 2.当前排列的递推结果

根据当前排列递推后所有可能的结果作为我们的划分依据

两种思考方式最后得到的状态计算图都是一样的

这是属于正拓扑顺序,从入度为0的点开始思考怎么推导,从而开始计算
状态表示:f[a,b,c,d,e]
集合:所有第一排有a个人,第二个排有b个人…第五排有e个人的方案
属性:总方案数
状态计算
从头向尾考虑,考虑我们如何安排第一个人,这个人如何推导到下一个父集合

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N=31;
int kk[6];
typedef long long ll;
ll dp[N][N][N][N][N];
int main()
{
    int k;
    while(cin>>k,k)
    {
        memset(dp,0,sizeof dp);
        memset(kk,0,sizeof kk);
        for(int i=0;i<k;i++)
            cin>>kk[i];
        dp[0][0][0][0][0]=1;
        for(int a=0;a<=kk[0];a++)
            for(int b=0;b<=min(a,kk[1]);b++)
                for(int c=0;c<=min(b,kk[2]);c++)
                    for(int d=0;d<=min(c,kk[3]);d++)
                        for(int e=0;e<=min(d,kk[4]);e++)
                        {
                            ll &x=dp[a][b][c][d][e];
                            if(a<kk[0])
                                dp[a+1][b][c][d][e]+=x;
                            if(b<kk[1])
                                dp[a][b+1][c][d][e]+=x;
                            if(c<kk[2])
                                dp[a][b][c+1][d][e]+=x;
                            if(d<kk[3])
                                dp[a][b][c][d+1][e]+=x;
                            if(e<kk[4])
                                dp[a][b][c][d][e+1]+=x;
                        }
        
        cout<<dp[kk[0]][kk[1]][kk[2]][kk[3]][kk[4]]<<'\n';
        
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值