acm暑校第五题——复杂的整数划分

一.题目——复杂的整数划分问题

总时间限制: 

200ms

内存限制: 

65536kB

描述

将正整数n 表示成一系列正整数之和,n=n1+n2+…+nk, 其中n1>=n2>=…>=nk>=1 ,k>=1 。
正整数n 的这种表示称为正整数n 的划分。

输入

标准的输入包含若干组测试数据。每组测试数据是一行输入数据,包括两个整数N 和 K。
(0 < N <= 50, 0 < K <= N)

输出

对于每组测试数据,输出以下三行数据:
第一行: N划分成K个正整数之和的划分数目
第二行: N划分成若干个不同正整数之和的划分数目
第三行: N划分成若干个奇正整数之和的划分数目

样例输入

5 2

样例输出

2
3
3

提示

第一行: 4+1, 3+2,
第二行: 5,4+1,3+2
第三行: 5,1+1+3, 1+1+1+1+1+1

二.DFS——Time Limit Exceeded

写出来了DFS,结果发现超时。

#include<bits/stdc++.h>
using namespace std;
int N,K;
int a[100];
int sum=0;
void divide1(int n,int k)
{
    if(n==0&&k==K)
    {
        sum+=1;
        return ;
    }
    if(n<=0||k>=K)
    {
        return ;
    }
    for(int i=n;i>=1;i--)
    {
        divide1(n-i,k+1);
        
    }
 }
 void divide2(int n,int k)
{
    if(n==0&&k==K)
    {
        sum+=1;
        return ;
    }
    if(n<=0||k>=K)
    {
        return ;
    }
    for(int i=n;i>=0;i--)
    {
        divide2(n-i,k+1);
        
    }
 }
void divide3(int n,int k)
{
    if(n==0)
    {
        sum+=1;
        return ;
    }
    if(n<=0)
    {
        return ;
    }
    for(int i=N;i>=0;i--)
    {
        if(i%2==1)
        divide3(n-i,k+1);
        
    }
 } 
int main()
{
    
    cin>>N>>K;
    divide1(N,0);
    cout<<(sum+1)/2<<endl;
    sum=0;
    divide2(N,0);
    cout<<(sum+1)/2<<endl;
    sum=0;
    divide3(N,0);
    cout<<(sum+1)/2<<endl;
    return 0;
}

三.动态规划——Accepted

网上学习了大佬的代码

原文链接如下:

复杂整数划分问题(动态规划)_复杂的整数划分问题-CSDN博客

1. N划分成K个正整数之和的划分数目

注意此处的限定,N必须划分成K个正整数,不允许有0的存在,所以j<=i。从另一个角度讲,dp[i][j]划分出的数里最大的必须是j,于是:
(1)j=i,dp[i][j]=1,因为划分的最大数必须是i,就一种情况;
(2)j<i,dp[i][j]=dp[i-1][j-1]+dp[i-j][j],这里是按照划分中最小数是否为1推出的,当最小数为1时,首先从i中取出1,占用1部分,然后把i-1分为j-1部分,dp[i-1][j-1]。当最小数比1大时,先给每一个部分都放一个1进去,花费j*1,再将i-j分到这j个部分里,dp[i-j][j]。

2. N划分成若干个不同正整数之和的划分数目

将N分成若干部分,但是每一部分值都不同,考虑dp[i][j],此处,理解为数字i的划分里最大值为j,因为要考虑不同的数,这样理解显然更容易解题。
(1)j=i,dp[i][j]=1+dp[i][j-1],当最大数为i时有 1 种情况,这里并不强制最大数必须为i,所以当最大数不为i时有dp[i][j-1]种可能;
(2)j<i,dp[i][j]=dp[i-j][j-1]+dp[i][j-1],最大数为j时,先从i中取出j,再对i-j进行划分,由于不允许划分重复,最大数变为j-1,dp[i-j][j-1]。最大数不为j时,那么最大为j-1,dp[i][j-1];
(3)j>i,dp[i][j]=dp[i][i],这里应该很容易理解,最大数不可能超过它自身。

3.N划分成若干个奇正整数之和的划分数目

这个问题我认为是随着理解角度的不同,解题复杂性就差的多了,如果将dp[i][j]理解为整数i划分成j个奇数,那解起来就麻烦了,我也查阅了很多资料,这种方法是最容易找到的,我不再叙述;此处最好的理解应该是整数i的划分里最大的数是j。

首先,我们要求将N划分为多个奇数,对于dp[i][j],j为偶数表示划分里最大的数为偶数,那么它实质上只能取得的最大数为j-1(奇数),所有j%2=0,dp[i][j]=dp[i][j-1],之后讨论j为奇数的情况。

(1)j=i,dp[i][j]=1+dp[i][j-1],当最大数为i时有 1 种情况,这里并不强制最大数必须为i,所以当最大数不为i时有dp[i][j-1]种可能;
(2)j<i,dp[i][j]=dp[i-j][j]+dp[i][j-1],最大数为j时,先从i中取出j,再对i-j进行划分,允许划分重复,最大数依然为j,dp[i-j][j]。最大数不为j时,那么最大为j-1,dp[i][j-1];
(3)j>i,dp[i][j]=dp[i][i],这里同上。
 

#include <iostream>
using namespace std;
#include <cstring>

int main()
{
    int N=0, K=0;//数N
    int dp1[51][51]; int dp2[51][51]; int dp3[51][51];
    while (cin >> N >> K)
    {
        //N分成K个正数,要注意这里不允许出现0,所以j<=i,i最多分成j个非0数
        //dp[i][j],可以理解为该划分中一定会出现 j 
        memset(dp1, 0, sizeof(dp1));
        dp1[0][0] = 1;
        for (int i = 1; i <= N;++i)
        {
            //dp1[i][1] = 1;
            for (int j = 1; j <= i; ++j)//这里的j不大于i,
            {                
                if (i == j)    //一定出现i,就一种
                    dp1[i][j] = 1 ; 

                else           //划分中出现1 + 划分中都比1大
                    dp1[i][j] = dp1[i - 1][j - 1] + dp1[i - j][j];
            }
        }
        cout << dp1[N][K] << endl;

        //N分成若干不相同的整数
        memset(dp2, 0, sizeof(dp2));
        dp2[0][0] = 1;
        for (int i = 1; i <= N; ++i)
        { 
            for (int j = 1; j <=N; ++j)
            {
                //dp2[0][j] = 1;
                if (j == i)
                    dp2[i][j] = dp2[i][j - 1] + 1;
                else if (j > i)
                    dp2[i][j] = dp2[i][i];
                else           //划分中出现了j则下一次最大到j-1;
                               //划分中不出现j,下一次同样不允许出现j
                    dp2[i][j] = dp2[i - j][j - 1] + dp2[i][j - 1];
            }
        }
        cout << dp2[N][N] << endl;

        //N分成若干个奇数
        memset(dp3, 0, sizeof(dp3));
        dp3[0][0] = 1;
        for (int i = 1; i <= N; i++)
        {
            //dp3[i][1] = i % 2 ? 0 : 1;
            //dp3[i][0] = 0;
            for (int j = 1; j <= N; j++)
            {
                if (j % 2 == 0)//此处理解为划分中含有j,如果j为偶数,显然直接到j-1
                    dp3[i][j] = dp3[i][j - 1];

                else     //以下三个分支中都将dp3[i][j]理解为划分中含有j
                {
                    if (i < j)    
                        dp3[i][j] = dp3[i][i];
                    else if (i == j)
                        dp3[i][j] = dp3[i][j - 1] + 1;
                    else
                        dp3[i][j] = dp3[i - j][j] + dp3[i][j - 1];
                }
            }
        }
        cout << dp3[N][N] << endl;
    }
    return 0;
}

四.DFS与动归解决这道问题的复杂度比较

1.第一段代码:递归方法
函数 divide1(int n, int k):

递归地尝试将 n 分成 K 个正整数。
在每一层递归中,从 n 遍历到 1,调用下一层递归。
时间复杂度为 O(K * N^K)。因为每层递归有最多 N 种选择,总共有 K 层递归。
函数 divide2(int n, int k):

递归地尝试将 n 分成若干不同的正整数。
在每一层递归中,从 n 遍历到 0,调用下一层递归。
时间复杂度与 divide1 类似,也是 O(K * N^K)。
函数 divide3(int n, int k):

递归地尝试将 n 分成若干个奇数正整数。
在每一层递归中,从 N 遍历到 0,只选择奇数,调用下一层递归。
时间复杂度仍然是 O(K * N^K)。

总结
由于这些函数的递归深度和每层递归中的循环次数都与 N 和 K 成指数关系,递归方法的时间复杂度非常高,尤其对于较大的 N 和 K 值来说,容易造成时间超时。

2.第二段代码:动态规划方法
第一类划分(dp1 数组):

使用二维数组 dp1[i][j] 存储将 i 分成 j 个正整数之和的划分数目。
两层循环,外层循环遍历 i 从 1 到 N,内层循环遍历 j 从 1 到 i。
时间复杂度为 O(N^2)。
第二类划分(dp2 数组):

使用二维数组 dp2[i][j] 存储将 i 分成若干个不同的正整数之和的划分数目。
两层循环,外层循环遍历 i 从 1 到 N,内层循环遍历 j 从 1 到 N。
时间复杂度为 O(N^2)。
第三类划分(dp3 数组):

使用二维数组 dp3[i][j] 存储将 i 分成若干个奇数正整数之和的划分数目。
两层循环,外层循环遍历 i 从 1 到 N,内层循环遍历 j 从 1 到 N,但仅处理奇数。
时间复杂度为 O(N^2)。

总结
动态规划方法使用了二维数组来存储中间结果,每个子问题的解只需要常数时间进行组合,避免了递归方法中的大量重复计算,因此其时间复杂度显著降低。

3.总结
递归方法:时间复杂度为 O(K * N^K),容易导致时间超时。
动态规划方法:时间复杂度为 O(N^2),在 N 和 K 的限制范围内(0 < N <= 50, 0 < K <= N)可以高效解决问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值