【计数dp】| AcWing 算法基础班试题总结

900. 整数划分

题目描述

一个正整数n可以表示成若干个正整数之和,形如:n=n1+n2+…+nk,其中n1≥n2≥…≥nk,k≥1。

我们将这样的一种表示称为正整数n的一种划分。(无序、可重复)

现在给定一个正整数n,请你求出n共有多少种不同的划分方法。

输入格式
共一行,包含一个整数n。

输出格式
共一行,包含一个整数,表示总划分数量。

由于答案可能很大,输出结果请对109+7取模。

数据范围
1≤n≤1000

输入样例:

5

输出样例:

7

扫描二维码关注公众号,回复: 12871317 查看本文章

思路一:完全背包思想

思路: 把整数1,2,3, … n分别看做n个物体的体积,这n个物体都没有使用次数限制,问恰好能装满总体积为n的背包的总方案数(完全背包问题变形)

状态表示: f[i][j]表示前i个整数(1,2…,i)恰好凑成整数j的方案数
求方案数:把集合选0个i,1个i,2个i,…,s个i全部加起来,其中s*i<=j,(s+1)*i>j
f[i][j] = f[i - 1][j] + f[i - 1][j - i] + f[i - 1][j - 2*i] + ...+ f[i][j - s*i] ;
f[i][j - i] =      f[i - 1][j - i] + f[i - 1][j - 2 * i] + ...+ f[i][j - s*i];
因此有优化后的状态转移方程 f [ i ] [ j ] = f [ i − 1 ] [ j ] + f [ i ] [ j − i ] ; f[i][j]=f[i−1][j]+f[i][j−i]; f[i][j]=f[i1][j]+f[i][ji];

初始化问题:
由于i-1、j-i的存在,可能涉及第0行、第0列。
且我们从数字1,即第一行开始递推,就要用到第0行的初始化:
f[0][j],即第0行,不选择一个数选择凑到数字j,显然不可能,所以除了f[0][0],都初始化为0,什么都不选也可以凑到数字0,所以f[0][0]=1
f[i][0] 当给i个数字凑数字0时,那么什么都不选就可以了,所以f[i][0]=1

而我们只要初始化第0行就可以了,这样下面计算的时候让j从0开始循环,就可以让每一行的f[i][0]=1了。

#include <iostream>

using namespace std;

const int N = 1e3 + 7, mod = 1e9 + 7;

int f[N][N];

int main() {
    
    
    int n;
    cin >> n;

    f[0][0] = 1; "容量为0时,前 i 个物品全不选也是一种方案,凑够数字0,将第0行的f[0][0]初始化为0"
    
    for (int i = 1; i <= n; i ++) {
    
    
        for (int j = 0; j <= n; j ++) {
    
     "j从0开始"
            f[i][j] = f[i - 1][j]; "同时处理j=0的列"
            if (j >= i) f[i][j] = (f[i - 1][j] + f[i][j - i]) % mod;
        }
    }
    cout << f[n][n] << endl;
    return 0;
}

一维数组优化:
观察 f [ i ] [ j ] = f [ i − 1 ] [ j ] + f [ i ] [ j − i ] ; f[i][j]=f[i−1][j]+f[i][j−i]; f[i][j]=f[i1][j]+f[i][ji];

当按行更新时,即先循环行,再循环列,求前i个数字下凑各种数字j的各种方案数,那么就可以只用到第i行和第i-1行,就可以优化为一维数组存储。
按照正序遍历,因为前面更新过的要被后面的用到。

算法实现

#include <iostream>
#define read(x) scanf("%d",&x)

using namespace std;

const int N=1010,mod=1e9+7;
int dp[N];

int main() {
    
    
    int n;
    read(n);
 "初始化第0行"
    dp[0]=1;
    
 "每次更新一行,先循环i,再循环j,保证可以得到需要的数据"
    for (int i=1;i<=n;i++)
        for (int j=i;j<=n;j++)  "正序遍历"
        	dp[j]=(dp[j]+dp[j-i])%mod;
    
    printf("%d",dp[n]);    
    
    return 0;
}

思路二:人为划分集合

f[i][j]表示i的j划分,即总和为i,数字i被用j个正数表示的方案数。表示的数字范围从1到n。
人为的将j个数的集合划分成两部分:是否包含数字1?如果包含,可以把1踢出去:如果不包含,可以让集合整体减1:

动态转移方程f[i][j]=f[i-1][j-1]+f[i-j][j];

最终的结果:即数字n被j个数表示的方案总和,j=1,2,……,n,所以要求f[n][1]f[n][n]的累加和。

初始化问题:
f[0][j],j个正数表示数字0; f[i][0],0个正数表示数字i。
第一行、第一列除了f[0][0]外都初始化为0,f[0][0]=1,因为0个正数可以表示数字0。

对于二维表f,主对角线处f[i][i]指i个数表示数字i,必然全为1,只有一种情况,所以f[i][i]=1
       第一列f[i][1],数字i用1个数表示,这个是肯定是i,所以f[i][1]=1

此外,这个最终的二维表f是个下三角矩阵,因为对于数字i,由j个数相加而成,j的最大值就是i。 i个1相加。

不能优化成一位数组:

  1. 按行更新时,求第i行要用到第i-1行和第i-j行,不是用到了两行,不可。
  2. 按列更新时,求第j列时,虽然只会用到j列和第j-1列,但我们最终需要二维表的第n行,按列更新时最终只会记录第j列,得不到我们要的最终结果。

算法实现

#include <iostream>
#define read(x) scanf("%d",&x)

using namespace std;

const int N=1010,mod=1e9+7;
int dp[N][N];

int main()
{
    
    
    int n;
    read(n);
    //初始化
    dp[0][0]=1;
    
    for (int i=1;i<=n;i++)
        for (int j=1;j<=i;j++)  
            dp[i][j]=(dp[i-1][j-1]+dp[i-j][j])%mod;
    
    int res=0;
    for (int i=1;i<=n;i++) res=(res+dp[n][i])%mod;
    printf("%d",res);    
    
    return 0;
}

思路三:分治

状态表示:
f[n][m]表示将数字n最多分成m份,最多m个数相加的n。

LL solve(int n , int m ){
    
      
    "如果n == 1    显然只能分成一组 "
    "如果m == 1    显然只能分成一组 "
    if( n == 1 || m == 1)    return 1 ;

    "如果n > m "
    "分成m份:那么给集合中每个数减去1,此时份数不变,等价于=> solve(n-m,m)"
    "不分成m份: 递归求分成m-1份=>solve(n,m-1)"
    if( n > m ) return (solve(n-m,m)+solve(n,m-1))%mod;

    "如果n == m "
    "情况同上,n > m,只不过分成n份时结果已知为1,就不用再去减1了, 不分成m份时=>solve(n,m-1)"
    if(n == m ) return  (1+solve(n,m-1))%mod ;
    
    "如果n < m , solve(n,m) == solve(n,n)"
    if(n < m )  return solve(n,n)%mod ; 
}

C++代码

#include <iostream>

using namespace std;

const int N = 1010,mod = 1e9+7;

int f[N][N];

int main()
{
    
    
    int n; 
    cin >> n; 

    for(int i = 1 ; i <= n ; i++)   f[1][i] = f[i][1] = 1;     "第一行第一列才都初始化为1"

    for(int i = 2 ; i <= n ; i++ ){
    
    
        for(int j = 2; j <= n ; j++ ){
    
      "没有限制j<=i,所以上面第一行第一列才都初始化为1"
            if(i == j)  f[i][j] = (1+f[i][j-1])%mod ;
            else if(i > j)   f[i][j] = (f[i-j][j]+f[i][j-1])%mod ;
            else    f[i][j] = f[i][i]%mod; 
        }
    }

    cout << f[n][n] << endl;

    return 0;
}

猜你喜欢

转载自blog.csdn.net/HangHug_L/article/details/114437100