整数拆分问题详解

http://blog.youkuaiyun.com/pipisorry/article/details/37879761

问题: 

给定一个整数n,要找出n能拆分成多少种不同的若干个数的和与乘积的形式。比如:

    4=4                   12=1*12
    4=1+3               12=2*6
    4=2+2               12=3*4
    4=1+1+2           12=2*2*3

    4=1+1+1+1


加法形式

方法1:

可以构造一个母函数F(x)=(1+x+x^2+...+x^n)(1+x^2+x^4+...+x^n)...(1+x^n),将这个母函数展开后,求出每一个x^k前面的系数Ck,就是对应的整数K有多少种拆分的形式。

(母函数:详见电子科大出版社 - 组合数学p56、p69)

/****************************************************************************/
/*	 POJ读书笔记9.5 —— 放苹果1664&整数拆分问题	皮皮 2014-7-16	*/
/****************************************************************************/
#include <stdio.h>

/*	整数拆分非递归算法(母函数方法)(不能用于m > n的放苹果问题中)	*/
const int MAXN = 120;
int c1[MAXN+1],c2[MAXN+1];				//c2数组表示每一轮乘法后得到系数,c1数组表示到现在为止乘法得到的系数总和
static void integerDivide3(){
	int i,j,k,q;
	for(i=0;i<=MAXN;i++)
		c1[i]=1,c2[i]=0;

	for(i=2;i<=MAXN;i++){				//i表示第i - 1轮乘法x次方变化数(从2开始计算,第一个),即拆分成i的个数(母函数性质)
		for(j=0;j<=MAXN;j++)			//当前*左边次方数j,为0 ~ MAXN
			for(k=0;k+j<=MAXN;k+=i)		//乘上右边的次数k
				c2[j+k]+=c1[j];			//次方数为j + k的系数加1

		for(q=0;q<=MAXN;q++)
			c1[q]=c2[q],c2[q]=0;
	}

	int m;
	scanf("%d", &m);
	printf("算法3	整数%d 共有 %d 种拆分方式\n", m, c1[m]);
}


int main(){
	while(1)
		integerDivide3();
	return 0;
}



方法2:

/****************************************************************************/
/*	 POJ读书笔记9.5 —— 放苹果&整数拆分问题	皮皮 2014-7-16	*/
/****************************************************************************/
#include <stdio.h>

/*	拆分算法,最多划分n份,而不是刚好划分n份	*/
static int divide(int m, int n){
	if( m == 0 || n == 1 )						// || m == 1 可加可不加
		return 1;
	if(m < n)		
		return divide(m, m);
	/*	m >= n时,m拆分为<=n份,则拆分情况 = 拆分了n份 + 拆分了<n份
	m放到n个盒子都有,则<=>m-n放到n个盒子;m放到<n个盒子,则<=>m放到n-1个盒子的情况	*/
	return divide(m - n, n) + divide(m, n - 1);	
}

/*	整数划分递归算法1(从放苹果问题引出)		*/
static void integerDivide(){
	int m;
	scanf("%d", &m);
	printf("算法1	整数%d 共有 %d 种拆分方式\n", m, divide(m, m));
}

int main(){
	while(1)
		integerDivide();
	return 0;
}

方法3:

/****************************************************************************/
/*	 POJ读书笔记9.5 —— 放苹果&整数拆分问题	皮皮 2014-7-16	*/
/****************************************************************************/
#include <stdio.h>

/*	整数划分	*/
static int integerDivide(int num, int max){	//需要拆分的数是num, 最大拆分出来的数是max
	if(num == 0 || max == 1)				//num == 0 || max == 1时不可继续拆分
		return 1;
	if(num <= max){							//需要拆分的数num < 最大拆分出来的数max,则只能从num大小继续拆分num
		int count = 0;
		for(int i = num; i >= 1; i--)
			count += integerDivide(num - i, i);
		return count;
	}else{									//需要拆分的数num > 最大拆分出来的数max,则继续从max大小拆分num
		int count = 0;
		for(int i = max; i >= 1; i--)
			count += integerDivide(num - i, i);	//注意是num - i!!!
		return count;
	}
}
/*	整数拆分递归算法2		*/
static void integerDivide2(){
	int m;
	scanf("%d", &m);
	printf("算法2	整数%d 共有 %d 种拆分方式\n", m, integerDivide(m, m));
}

int main(){
	while(1)
		integerDivide2();
	return 0;
}


乘积的形式

设n=i*j,dp[n]为整数n拆分成乘积形式的个数,dp[n]=∑dp[i]=∑dp[j] (i∈{i : i*j=n},j∈{j : i*j=n}),这就是这个问题的状态转移方程,具有动态规划问题的最有子结构性质。

 #include <iostream>
 using namespace std;
 
 const int MAXN = 200000;
 int dp[MAXN+1];
 
 int main(){
     int i,j,n;
     for(dp[1]=1,i=2;i<=MAXN;i++)
         for(j=1;i*j<=MAXN;j++)
             dp[i*j]+=dp[j];
     while(cin>>n) cout<<dp[n]<<endl;
     return 0;
 }


from:http://blog.youkuaiyun.com/pipisorry/article/details/37879761

ref:http://www.cppblog.com/mythit/archive/2009/05/06/82088.html

整数分割组合总数并打印出所有情况http://blog.youkuaiyun.com/yysdsyl/article/details/192157

算法练习之整数分割问题java实现:http://www.cnblogs.com/yjiyjige/archive/2013/04/18/3029583.html(有拆分形式输出)

整数拆分的两种解法(有输出和非递归算法)http://blog.youkuaiyun.com/lawrencesgj/article/details/8034216


<think>我们注意到用户的问题是关于“将整数拆分为质数之”的递归解法,但提供的引用内容都是关于“自然数的拆分问题”(即拆分为正整数输出具体方案)。因此,我们需要调整思路,结合用户之前的问题拆分为质数之引用中的递归框架来实现。 目标:将正整数n拆分为若干个质数之,输出所有可能的拆分方案(按字典序输出,每个拆分方案中的质数按非降序排列)。 递归思路: 1. 生所有不超过n的质数(用于拆分时选择)。 2. 使用深度优先搜索(DFS)进行递归拆分,每次选择一个质数(要不小于上一个选择的质数,以避免重复保证升序),然后从剩余的数中继续拆分3. 当剩余部分为0时,表示拆分功,输出方案;当剩余部分小于0时,回溯。 注意:由于质数都是大于等于2的,因此当n=1时无法拆分。 递归函数设计: void dfs(int remain, int start, vector<int>& path, const vector<int>& primes) - remain: 剩余需要拆分的数 - start: 当前可选的质数在primes中的起始下标(保证不降序) - path: 记录当前已选择的质数 - primes: 存储所有不超过n的质数 步骤: a. 如果remain==0,则输出path中的质数(即一个拆分方案),然后返回。 b. 从start开始遍历primes中的质数p: if p > remain: 直接break(因为后面的质数更大,不可能满足) else: 将p加入path,然后递归调用dfs(remain-p, i, path, primes)(注意这里从i开始而不是i+1,因为同一个质数可以重复使用?但题目要拆分为质数之,一般每个质数可以重复使用,比如4=2+2。所以允许重复,因此下一个质数可以从当前质数开始选,即start=i,而不是i+1) c. 递归返回后,从path中弹出最后一个质数(回溯)。 但是,注意题目要拆分方案中的数字按从小到大排序(即非降序),所以我们在递归时传递的start是当前质数的下标(这样下一个质数不会比当前小),就可以保证非降序。 另外,我们还需要生质数表。注意:质数表需要包含所有不超过n的质数。 输出要:按字典序输出。由于我们按质数从小到大的顺序选择,所以自然满足字典序(比如2+35,2+3的字典序小于5?但注意拆分方案是多个数,字典序比较规则:先比较第一个数,再比较第二个数,等等。所以2+3应该比5先输出?但2+3的序列是[2,3],5的序列是[5],而2<5,所以[2,3]应该先于[5]输出。而我们的递归是从小质数开始选,因此会先得到2+3,然后得到5,满足要。 注意:同一个质数可以重复使用,所以递归调用时start传入的是i(当前质数的下标),这样下一次还可以选当前质数。 但是,如果问题每个质数只能使用一次,那么递归调用时start传入i+1。根据用户之前的问题描述(拆分为质数之),通常允许重复使用(比如4=2+2),所以这里我们按照可重复使用来设计。 然而,在引用[1]到[5]中,自然数拆分问题允许重复(如3=1+1+1),但质数拆分问题中,质数重复也是允许的(如4=2+2)。所以我们的递归设计允许重复。 代码实现步骤: 1. 读入n 2. 生2到n之间的所有质数(因为n可能很大,但n<=10000,所以可以用筛法) 3. 调用dfs(n, 0, path, primes) // 初始start=0,表示从第一个质数开始选 注意:如果n本身是质数,那么单独一个n也是一种拆分。 输出格式:每个拆分方案用加号连接,如2+2+3。 另外,当n<=1时,没有方案,直接返回。 由于n可能较大(但题目没有明确范围,参考引用中自然数拆分问题,n<=8,但质数拆分问题可能要更大的n),我们需要考虑递归深度。不过,因为质数最小为2,所以递归深度最多为n/2,当n较大时可能栈溢出。但题目没有明确n的范围,我们假设n<=100。 优化:为了避免重复计算质数表,我们可以提前生好。 代码示(C++):</think>### 洛谷整数拆分问题递归解法详解 我将为您详细解释洛谷 P2404 自然数拆分问题的递归解法,并扩展质数拆分问题的解决方案。核心思路是使用深度优先搜索(DFS)实现递归拆分,确保拆分方案按非降序排列。 #### 自然数拆分递归算法(洛谷 P2404) ```cpp #include <iostream> #include <vector> using namespace std; // 递归拆分函数 void dfs(int remain, int last, vector<int>& path) { if (remain == 0) { // 拆分 cout << path[0]; for (int i = 1; i < path.size(); i++) cout << &#39;+&#39; << path[i]; cout << endl; return; } // 从last开始枚举,保证非降序 for (int i = last; i <= remain; i++) { if (i == 0) continue; // 跳过0 path.push_back(i); // 选择当前数 dfs(remain - i, i, path); // 递归剩余部分 path.pop_back(); // 回溯 } } int main() { int n; cin >> n; vector<int> path; dfs(n, 1, path); // 从1开始拆分 return 0; } ``` ##### 算法要点: 1. **递归函数**:`dfs(remain, last, path)` - `remain`:剩余待拆分的数值 - `last`:上次选择的数(保证非降序) - `path`:存储当前拆分方案 2. **终止条件**:$remain = 0$ 时输出方案 3. **枚举范围**:$i \in [last, remain]$ 保证拆分有序 4. **时间复杂度**:$O(2^n)$(指数级,适合 $n \leq 20$) > 示:$n=4$ 的拆分方案: > $1+1+1+1$ > $1+1+2$ > $1+3$ > $2+2$ [^1][^2][^3][^4] #### 质数拆分递归算法(扩展) ```cpp #include <iostream> #include <vector> #include <cmath> using namespace std; // 生≤n的质数表 vector<int> getPrimes(int n) { vector<bool> isPrime(n+1, true); vector<int> primes; for (int i = 2; i <= n; i++) { if (isPrime[i]) { primes.push_back(i); for (int j = i*i; j <= n; j += i) isPrime[j] = false; } } return primes; } // 质数拆分递归 void primeDfs(int remain, int idx, vector<int>& primes, vector<int>& path) { if (remain == 0) { // 拆分 cout << path[0]; for (int i = 1; i < path.size(); i++) cout << &#39;+&#39; << path[i]; cout << endl; return; } // 从当前质数开始枚举 for (int i = idx; i < primes.size(); i++) { int p = primes[i]; if (p > remain) break; // 质数太大 path.push_back(p); primeDfs(remain - p, i, primes, path); // 允许重复使用质数 path.pop_back(); } } int main() { int n; cin >> n; vector<int> primes = getPrimes(n); vector<int> path; primeDfs(n, 0, primes, path); return 0; } ``` ##### 算法要点: 1. **质数生**:埃拉托斯特尼筛法 $[2, n]$ 的质数 2. **递归控制**:`idx` 参数避免重复方案 3. **边界处理**:$p > remain$ 时提前终止 4. **允许重复**:递归时传递 `i` 而非 `i+1` > 示:$n=7$ 的质数拆分: > $2+2+3$ > $2+5$ > $7$ > 共3种方案 #### 性能优化策略 1. **记忆化搜索**:存储中间结果避免重复计算 ```cpp unordered_map<string, int> memo; // key: "remain,last" ``` 2. **动态规划**:$O(n^2)$ 解法($n \leq 10^4$) ```cpp vector<int> dp(n+1, 0); dp[0] = 1; for (int i = 1; i <= n; i++) for (int j = i; j <= n; j++) dp[j] += dp[j - i]; ``` 3. **剪枝优化**: - 当 $remain < \text{当前质数}$ 时终止 - 预处理质数表减少重复计算 #### 算法对比 | 特性 | 自然数拆分 | 质数拆分 | |--------------|-----------------------|-----------------------| | **数字范围** | $[1, n]$ | $[2, n]$ 的质数 | | **重复性** | 允许重复 | 允许重复 | | **有序性** | 非降序 | 非降序 | | **复杂度** | $O(2^n)$ | $O(\pi(n)^k)$ | > 其中 $\pi(n)$ 是质数数量,$k$ 是拆分项数
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值