3

基础实验2-2.5整数分解为若干项之和

将一个正整数N分解成几个正整数相加,可以有多种分解方法,例如7=6+1,7=5+2,7=5+1+1,…。编程求出正整数N的所有整数分解式子。
输入格式:
每个输入包含一个测试用例,即正整数N (0<N≤30)。
输出格式:
按递增顺序输出N的所有整数分解式子。递增顺序是指:对于两个分解序列N​1​​={n​1​​,n​2​​,⋯}和N​2​​={m​1​​,m​2​​,⋯},若存在i使得n​1​​=m​1​​,⋯,n​i​​=m​i​​,但是n​i+1​​<m​i+1​​,则N​1​​序列必定在N​2​​序列之前输出。每个式子由小到大相加,式子间用分号隔开,且每输出4个式子后换行。
输入样例:

7   

输出样例1:

7=1+1+1+1+1+1+1;7=1+1+1+1+1+2;7=1+1+1+1+3;7=1+1+1+2+2
7=1+1+1+4;7=1+1+2+3;7=1+1+5;7=1+2+2+2
7=1+2+4;7=1+3+3;7=1+6;7=2+2+3
7=2+5;7=3+4;7=7

题目:基础实验2-2.5 整数分解为若干项之和 (20分)

算法分析

 看到这题第一反应是减而治之。很像全排列问题,先写出第一项,再写出剩下的数的分解项。一起来分析步骤。
 首先假设我们要求 $F(4)$, 即写出 $n=4$ 的所有分解项。那么我们可以先写出第 1 位的分解项的所有可能。即 $k_1=1,2,3,4$。对于每个 $k_1$,我们只需求解它的子问题 $F(n-k_1)$ 。
 例如:对于 $F=4$ 的情况,第一位 $k_i$ 所有可能的值为 $k_i=1,2,3,4$ 。对于 $k_i=1$,问题被分解成了 $F(4)= 1 + F(3)$ 。递归这个过程,得到一棵如下的树:
image.png

 由于题目中要求结果序列升序排列,所以我们要去除重复情况,分析后发现:

  1. 对于重复情况,结果序列会出现一个降序的子序列。即为了不重复,我们要保证 $k_{i-1}<=k_i$ 。

整理一下,可得:处于区间 $[0,k_{i-1}-1]\bigcup(\lfloor\frac n2\rfloor,n)$ 的情况必须舍去,我们只需要区间 $[k_{i-1},\lfloor\frac n2\rfloor]\bigcup\{n\}$ 便可。
对于 $F=4$ ,我们可以看做是 $F(5)=1+F(4)$ ,可得 $k_{i-1}=1$,带入刚才推导出的公式得第 $1$ 位的分解项的所有值为 $i=1,2,4$。
 对于 $i=1$ 这种情况,问题被分解成了:$1 + F(n-1) == 1 + F(3) $ 。递归得:
image.png

递归公式也不难写出来
$$F(n)=k_i+F(n-k_i),\ (n>0)$$

 相信看到这里很多同学已经能用DFS解出来了,估计核心代码在3、4行左右。但是看到几十层的递归栈······打扰了。我决定再推一下递推式,毕竟性能快好多不是。

下面根据递归式去推导递推。

 显然,F(n)的分解式不会超过 $n$ 项,也就是第一次DFS到底时, $\underbrace{1+1+1+···+1}_{n个1}$这种情况,设为序列 $U$。我们从这种情况开始反推。由于公式为 $F(n)=k_i+F(n-k_i)$, 将序列 $U$ 末尾2位代入公式,得 :

$$\begin{cases}k_{n-1}=1, \\n-k_{n-1}==n-1==1\Rightarrow n=2\end{cases}$$

此时再判定有无处于区间 $[k_{n-1},\lfloor\frac n2\rfloor]\bigcup\{n\}$ 的其他 $k_{n-1}$,有的话每次只需要处理比当前 $k_{n-1}$ 大 1 的情况,即如果 $j=\lfloor\frac n2\rfloor-k_{n-1}>0$,进行j次循环分解,每次执行 $n-=k_i+1$ ,遇到逆序情况则退出循环,最后存留的 $n$ 即为最后一位的值。与递归相反,整个递推过程当 $n=30$ 时自然结束递推了。

代码

 递归的代码网上都是,我就不传了,这边给出递推优化版本。经测试递归版本耗时400ms,递推版本11ms。

#include <stdio.h>
int main()
{
    int n = 0, a[31] = {0};
    scanf("%d", &n);
    for(int i=0; i<n; i++){
        a[i] = 1;
    } 
    int pos = n-1, temp = 0, cnt = 0;
    while(pos>=0){
        printf("%d=", n);
        for(int i=0; i<=pos; i++){
            if(i==pos){
                printf("%d", a[i]);
            }else{
                printf("%d+",a[i]);
            }
        }
        if(++cnt%4 == 0){
            printf("\n");
            cnt=0;
        }else{
            if(pos!=0) printf(";");
        }
        if(pos==0) break;
        int sum = a[pos] + a[pos-1];
        a[pos--] = 0;
        temp = a[pos] + 1;
        int j = (double)sum/2;
        int k = j - a[pos];
        while(k-->0){
            if(sum-temp < temp) break;
            sum -= temp;
            a[pos++] = temp;
        }
        a[pos] = sum;
    }
    return 0;
}

小结

 要培养减而治之和分而治之的思维方式。重剑无锋,大巧不工。


满眼星辰
67 声望8 粉丝

计算机硕士研究生。