思路:下降<=>上升

两种思路: 一. 1.递推中要确定最后一列的积木数量,就一定要直到前一个符合要求的方案最后一列的积木数量, 所以用[n块][m列][k最后一列数量]记录

求递推关系式,考虑最后一列,若最后一列使用的方块数<k,如从1,2,3,...,k-1,所有的方案数加起来等价于(n,m,k-1)

若最后一列使用的方块数=k(>k就不是这个数组元素了),那么就是最后一列用了k个方块=>先把前面m-1列摆好(n-k,m-1,k-1)

故[n][m][k]=[n][m][k-1]+[n-k][m-1][k-1]

int a[]          //j->m;i->n;k->隐式
a[0][0]=1//空排列也是一种排列
for(int k=1;k<=n;++k)//最终计算的相当于[n][m][k],为所需要的
for(int j=m;j>0;--j)//j从大到小,a[j][i]=a[j][i]+a[j-1][i-k];
//所以每个j-1都是后更新的,残留的是k-1;j=0时如果i>0,方案数为0,即默认初始化为0
for(int i=n;i>=k;--i)//n-k>=0
{
    a[j][i]+=a[j-1][i-k];  
}
>

二.依然用[n块][m列]记录 考虑拆分成子问题 第一列为1时,移除后列数减少。 第一列大于1时,移除后列数不变,但积木减少。 递归所有可能情况,判断边界条件。

a[n][m]=a[n-m][m-1]+a[n-m][m];

AC代码:

#include<iostream>
const int N=100,M=10;//N->个数,M->列数
using namespace std;
int dp[M+1][N+1];
int main()
{
    ios::sync_with_stdio(0);cin.tie(0);
    int t,n,m,i,j;cin>>t;
    dp[0][0]=1;
    for(j=1;j<=M;++j)
    {
        for(i=(j*(j+1))>>1;i<=N;++i) //这里相当于等差数列求和,严格单增要有m列,最少也是有(1+2+3+4+...+m)块
		{dp[j][i]=dp[j-1][i-j]+dp[j][i-j];}
    }
    while(t--)
    {
        cin>>n>>m;
        cout<<dp[m][n]<<'\n';
    }
    return 0;
}

拓展1:要是问n个积木,分成m垛,这些积木垛排成一行,使得所有垛的积木块数不相同,且呈“山“字形(积木数先单调上升,再单调下降)。比如6块积木,分成3垛,只有132,231两种方案。怎么办?

思考了半天,其实并不需要重新想递推式。只要微调前面得出的结果即可

#include<cmath>
cout<<(long long)(dp[m][n])*(long long)(pow(2,m-1)-2)<<'\n';
//相当于已经严格单增,除了最高(最后)那一列可以交换到它关于最后一列对称的位置
//乘法原理2**(m-1),但是如果全交换或全不交换又回到以前,所以排除这两种

数据过大的话要用long long算乘法

拓展2:你有n个积木块,堆成一排,共m列,要求每列都必须有积木,且每列上积木的数量不一样。如果你有6块积木,堆成3列,可以堆成“123”,“132”,“213”,“231”,“312”,“321”,一共六种。求方案数。

有了拓展1的铺垫这个就好想了。严格递增只是限定了顺序,并没有限定每列积木的数量,而且严格单增还保证了各列积木数不同,这下只要考虑n个数乱排,全排列Ann=n!;

long long fac[11]={1,1,2,6,24,120,720,5040,40320,362880,3628800};
cout<<(long long)(dp[m][n])*fac[m]<<'\n';