数的拆分(求更高效算法)

languagec 2007-10-08 11:02:11
u 问题描述:

将正整数n拆分成k份(使k个数之和的等于n),且每种拆分方案不能为空,任意两种拆分方案不能相同(不考虑顺序)。例如:n=7,k=3,共4种拆分方法为:①1、1、5; ②1、2、4; ③1、3、3; ④2、2、3。下面三种分法被认为是相同的:1、1、5; 1、5、1; 5、1、1;



u 编程任务:

给定的正整数n,分成k分,编程计算有多少种不同的分法。



u 数据输入:

输入数据只有一行2个数:n、k (6<n≤200, 2≤k≤6)。



u 结果输出:

输出数据是一个,有多少种不同的分法。



u 输入输出样例:

输入: 7 3

输出: 4

-------------------------------------


以下是我对该问题的解决代码。 算法就是硬搜,时间复杂度大概为 n^k 。100 6 还可以接受 200 6就无法忍受了,要1分来钟。

#include "stdio.h"
#include "stdlib.h"

int count;

void Select(int step,int cur,int n,int k,int sum)
{
int i;

if(sum>n) return;

if(step>k)
{
if(n==sum)
count++;
return;
}
for(i=cur;i<=n;i++)
{
Select(step+1,i,n,k,sum+i);
}
}

int main()
{
int n,k;
while(scanf("%d%d",&n,&k)!=EOF)
{
count=0;
Select(1,1,n,k,0);
printf("%d\n",count);
}
return 0;
}
...全文
1005 24 打赏 收藏 转发到动态 举报
写回复
用AI写文章
24 条回复
切换为时间正序
请发表友善的回复…
发表回复
wshcdr 2009-10-10
  • 打赏
  • 举报
回复
MK
zhouxulei 2007-10-12
  • 打赏
  • 举报
回复
HW121 朋友
能不能把8楼的代码注上注释,
看的不很明白。。。希望能拿具体例子解释下。


我基础不好,可否推崇本书?
dj0628 2007-10-12
  • 打赏
  • 举报
回复
mark
HW121 2007-10-12
  • 打赏
  • 举报
回复
zhouxulkei:
看18楼就能明白。

顺便更正一下18楼说的最快方法 复杂度为 (n-k)^2*lnk
HW121 2007-10-11
  • 打赏
  • 举报
回复
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <dos.h>
#include <memory.h>


unsigned __int64 split3(int n, int k);

unsigned __int64 splitn(int n, int k)
{
if(k>0 && n>=k)
return k==1? 1 : split3(n, k);
else
return 0;
}

unsigned __int64 split3(int n, int k)
{
if(k > 2) {
unsigned __int64 sum = 0;
while( n >= k) {
sum += split3(n-1, k-1);
n -= k;
}
return sum;
}
else
return n>>1;
}

unsigned __int64 split(int n, int k)
{
int nb, i, x;
unsigned __int64 *t1, *t2, *p1, *p2, *pt;
unsigned __int64 count = 0;

if( !(k>0 && n>=k) )
return 0;
if(k==1)
return 1;
if(k==2)
return n/2;

p1 = t1 = (unsigned __int64 *)calloc(n,sizeof(unsigned __int64));
p2 = t2 = (unsigned __int64 *)malloc(n*sizeof(unsigned __int64));
nb = n;
for(i = n; i >=k; i -=k)
p1[i-1]++; // f(x, k-1)
k--;
while(k > 2)
{
memset(p2, 0, sizeof(unsigned __int64)*n);
for(x=k; x < nb; x++)
if(p1[x])
for(i=x; i >=k; i-=k) // f(x, k) = g( f(x-1,k-1) )
p2[i-1] += p1[x];
pt = p1;
p1 = p2;
p2 = pt;
k--;
nb--;
}
for(x=k; x < nb; x++)
if(p1[x])
count += p1[x]*(x/2);

free(t1);
free(t2);

return count;
}

int main()
{
int n,k;
unsigned __int64 count;
clock_t start, end;
char buffer[65];

printf("unsigned __int64 max=%s \n", _ui64toa(-1, buffer,10));

n = 400;
k = 40;
start = clock();
count = split(n, k);
end = clock();
printf("split(%d,%d)=%s times: %f \n",n,k,_ui64toa(count, buffer,10), double(end - start) / CLK_TCK);

while(scanf("%d%d",&n,&k)!=EOF)
{
start = clock();
count = split(n, k);
end = clock();
printf("(call split ) count=%s times: %f \n", _ui64toa(count, buffer,10), double(end - start) / CLK_TCK);

start = clock();
count = splitn(n, k);
end = clock();
printf("(call splitn) count=%s times: %f \n", _ui64toa(count, buffer,10), double(end - start) / CLK_TCK);

}
return 0;
}
HW121 2007-10-11
  • 打赏
  • 举报
回复
在vc++2005 用上想法 编的函数unsigned __int64 splitf(int n,int k) 与 unsigned __int64 splitn(int n,int k)(去叶64位int)比较:

unsigned __int64 max=18446744073709551615
splitf(400,40)=171368906095602146 times: 0.000000
200
10
( call splitf ) count=807151588 times: 0.000000
( call splitn ) count=807151588 times: 5.390000
200
11
( call splitf ) count=1930988902 times: 0.000000
( call splitn) count=1930988902 times: 13.922000
200
12
( call splitf ) count=4068707635 times: 0.000000
( call splitn ) count=4068707635 times: 32.125000

HW121 2007-10-11
  • 打赏
  • 举报
回复
12楼 解题思路:
用g(n,k,s) 表示正整数n拆分成k个大于等于s 的拆分方案集, fg(n,k,s) 表示其方案数。
一、集合元素按由小到大有序排列,将集合第一个元素展开:
g(n,k,s)={s、g(n-s,k-1,s)}、{s+1、g(n-s-1,k-1,s+1)}、...{n/k、g(n-n/k,k-1,n/k)} (因为最小元素a1*k<=n,故a1<=n/k)
所以递推关系:
fg(n,k,s) = fg(n-s,k-1,s) + fg(n-s-1,k-1,s+1) + ... + fg(n-n/k,k-1,n/k)
二、g(n,1,s) = 1
这个方法优点:在计算过程中能够输出各个方案,缺点要遍历其元素构成深度为k的多叉树的所有节点才能得到其方案数。

15楼splitn是改进8楼后的程序:
用f(n,k) 表示正整数n拆分成k个正整数的拆分方案数 = fg(n,k,1)
如果将 g(n,k,s) 所有元素都减于s-1,则变为g(n-k*(s-1),k1)
fg(n,k,s) = f(n-k*(s-1),k)
=> f(n,k) = f(n-1,k-1) + f(n-1-k,k-1) + ... + f(n-n/k*(k-1),k-1)
f(n,1) = 1 => f(n,2)=n/2

看来改进也只是减少叶的部分,有没有更快的方法?有:
f(n,k) 分解的f(x,k-1)的各项的x都有x<n
可以用一个数组a记录f(x,k-1)出现次数
=> f(n,k) = ∑a[x]*f(x,k-1) (x>=k-1, n-1 )
= ∑b[x]*f(x,k-2)
现在问题就转化为对下个向量递推

可以预测单步(f(x,i)->f(x,i-1))复杂度为: n*n/i 全程复杂度为: n*n*ln(k)
languagec 2007-10-10
  • 打赏
  • 举报
回复
12楼
谢谢HW121 回复,实话说没有看懂,最近忙,过两天仔细看看。

13楼
arong1234 ,看了13楼,还是决定不骂你。

languagec 2007-10-10
  • 打赏
  • 举报
回复
11楼 zerglarva_aahha
非常感谢你的回复,前面两句话和后面两句话收下了,中间两句话留给你自己吧。 Y要再罗嗦连你一起骂!

我只是来问问题的,你爱答不答,其他扯皮的话别来这里讲,至于态度问题,还轮不到你来评论!

最后,谢谢各位XDJM的参与/ 各位的回复,让我大开眼界。

本帖也不是来求源码的,能积极思考就是好事了。。
HW121 2007-10-09
  • 打赏
  • 举报
回复
languagec(各有所求):
看懂我的方法没有,我的方法超快,只遍历(按拆分数由小到大顺序、深度为k的多叉树)所有方案一次。
因没对n<k情况判断,所以最好这样更好:
int split(int n, int k)
{
return (n<k)? 0 : split2(n, k, 1);
}
食人族哲学家 2007-10-09
  • 打赏
  • 举报
回复
汗,你想快,还搞递归。以我的理解递归只用于2中情况1.与新手学习有关(你教别人或者别人教你这个新手);2.在技术时间精力==都达不到找到一个非递归算法的情况。
还有,麻烦楼主端正点态度“废话这么多,没一个三角型说到了点子上,说了和没说还不一个样!
我也知道这是组合问题,我也知道用DP解决更快。可Y又不说怎么解决,和放P还不一个样! ”尽管灌水有点讨厌,不过有些还是还是很有提示意义的。
见过个类似的问题,没空写源代码,写点思路:看楼主自己给的例子,都知道从小到大排列,那么写程序时也这样搞不就没有重复的了吗。再看下排列组合算法,类似的解决办法就行了。
卖萌不杀 2007-10-09
  • 打赏
  • 举报
回复
当然,这个公式推出来之后。
你应该把它用动态规划的方法写,那么这样
的迭代方法应该是非常快的。
卖萌不杀 2007-10-09
  • 打赏
  • 举报
回复
[整数的分划问题]
对于一个正整数M的分划就是把M写成一系列正整数之和的表达式。例如,对于正整
数M=6,它可以分划为:
6
5+1
4+2, 4+1+1
3+3, 3+2+1, 3+1+1+1
2+2, 2+2+1+1, 2+1+1+1+1
1+1+1+1+1+1
注意,分划与顺序无关,例如5+1和1+5是同一种分划。另外,这个整数M本身也算
是一种分划。现在的问题是,对于给定的正整数M,要求编程序计算出其分划的数
目P(M)。


解析:
我们定义一个函数Q(M.N),表示整数M的“任何被加数都不超过N”的分划的数目。
Q(M.N)有以下递归关系:
1)Q(M,1)=1,表示当最大的被加数是1时,该整数M只有一种分划,即M个1相加;
2)Q(1,N)=1,表示整数M=1只有一个分划,不管最大被加数的上限N是多大;
3)Q(M,N)=Q(M,M),如果M<N。很明显,M的分划不可能包含大于M的被加数N;
4)Q(M,M)=1+Q(M,M-1)。等式的右边分为两部分:第一部分的1表示M只包含一个被
加数等于M本身的分划;第二部分表示M的所有其他分划的最大被加数N<=M-1,所以
其他分划的数目就是等式右边的第二项。
5)Q(M,N)=Q(M,N-1)+Q(M-N,N),如果M>N。等式右边的第一部分表示被加数中不包
含N的分划的数目;第二部分表示被加数中包含N的分划的数目,因为如果确定了一
个分划的被加数中包含N,则剩下的部分可以按照对M-N的分划进行划分。
上述五个递归关系式对Q(M,N)作了递归定义。因为M的所有分划的数目P(M)=Q(M,
M),所以上述关系式也定义了P(M)。

有了递归关系式后,解决问题的算法就非常简单了:

Partition(M, N)
1. if M < 1 or N < 1
2. then Error("输入参数错误")
3. else if M = 1 or N = 1
4. then return 1
5. else if M < N
6. then return Partition(M, M)
7. else if M = N
8. then return (1 + Partition(M, M-1))
9. else
10. return (Partition(M, N-1) + Partition(M-N, N));

请注意,正整数的划分的数目随着M的增加增长的非常快(以指数级增长),所以
不要用较大的整数来测试按照上述算法编写出的程序。
HW121 2007-10-09
  • 打赏
  • 举报
回复
输入数据 n、k (6 <n≤200, 2≤k≤6), 在这个范围运行即刻即出

#include <stdio.h>
#include <stdlib.h>
int split2(int n, int k, int s)
{
if(k > 1) {
int sum = 0;
for(int s2 = n/k;s <= s2; s++)
sum += split2(n-s, k-1, s);
return sum;
}
else
return 1;
}

int split(int n, int k)
{
return split2(n, k, 1);
}

void main()
{
printf("split(7,3)=%d\n", split(7,3));
printf("split(8,3)=%d\n", split(8,3));
printf("split(9,3)=%d\n", split(9,3));
printf("split(16,3)=%d\n", split(16,4));
printf("split(100,4)=%d\n", split(100,6));
printf("split(200,6)=%d\n", split(200,6));
}
zhaoyue12365 2007-10-09
  • 打赏
  • 举报
回复
看不懂你们的算法
languagec 2007-10-09
  • 打赏
  • 举报
回复
废话这么多,没一个三角型说到了点子上,说了和没说还不一个样!
我也知道这是组合问题,我也知道用DP解决更快。可Y又不说怎么解决,和放P还不一个样!

改了一下剪枝的方式,似乎又更快了一点。
#include "stdio.h"
#include "stdlib.h"

int count;

void Select(int step,int cur,int n,int k,int sum)
{
int i;

if(step+1>k)
{
if(n-sum>=cur)
count++;

return;
}
if(sum>n) return;

for(i=cur;i<=n;i++)
{
Select(step+1,i,n,k,sum+i);
}
}

int main()
{
int n,k;
while(scanf("%d%d",&n,&k)!=EOF)
{
count=0;
Select(1,1,n,k,0);
printf("%d\n",count);
}
return 0;
}
leechiyang 2007-10-09
  • 打赏
  • 举报
回复
其实这是一个排列组合问题,算算就出来了。有规律
leechiyang 2007-10-09
  • 打赏
  • 举报
回复
学习学习数论
jixingzhong 2007-10-09
  • 打赏
  • 举报
回复
我觉得可以试着直接计算 有多少种不同的分法,
而不是先拆分数据,然后sum统计 ...
HW121 2007-10-09
  • 打赏
  • 举报
回复
谁说递归很慢,Select是楼上的程序,split是8楼的程序(递归),splitn是改进8楼后的程序(递归),看结果:
200
6
(call Select) count=4132096 times: 0.188000
(call split ) count=4132096 times: 0.031000
(call splitn) count=4132096 times: 0.000000
200
8
(call Select) count=87914929 times: 5.640000
(call split ) count=87914929 times: 0.703000
(call splitn) count=87914929 times: 0.047000
200
10
(call Select) count=807151588 times: 79.234000
(call split ) count=807151588 times: 7.969000
(call splitn) count=807151588 times: 0.515000

附程序:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <dos.h>


int split3(int n, int k);
int split2(int n, int k, int s);

int split(int n, int k)
{
return (k>0 &&n>=k)? split2(n, k, 1) : 0;
}

int splitn(int n, int k)
{
if(k>0 && n>=k)
return k==1? 1 : split3(n, k);
else
return 0;
}

int split2(int n, int k, int s)
{
if(k > 1) {
int sum = 0;
for(int s2 = n/k;s <= s2; s++)
sum += split2(n-s, k-1, s);
return sum;
}
else return 1;
}

int split3(int n, int k)
{
if(k > 2) {
int sum = 0;
while( n >= k) {
sum += split3(n-1, k-1);
n -= k;
}
return sum;
}
else
return n>>1;
}


int Select(int number, int split)
{
int* ary = (int*)malloc(sizeof(int) * split);
int cur = 0;
int sum = 0;
int i = 0;
int count = 0;

for (; i < split; ++i)
ary[i] = 1;

while (1)
{
while (cur < split - 1 && sum + ary[cur] < number)
sum += ary[cur++];

if (cur == split - 1 && number - sum >= ary[split - 2])
++count;

if (cur - 1 < 0)
break;

sum -= ary[--cur]++;

for (i = cur + 1; i < split; ++i)
ary[i] = ary[cur];
}


if (ary)
free(ary);

return count;

}


int main()
{
int n,k;
while(scanf("%d%d",&n,&k)!=EOF)
{
int count;
clock_t start, end;

start = clock();
count = Select(n,k);
end = clock();
printf("(call Select) count=%d times: %f \n",count, double(end - start) / CLK_TCK);

start = clock();
count = split(n, k);
end = clock();
printf("(call split ) count=%d times: %f \n", count, double(end - start) / CLK_TCK);

start = clock();
count = splitn(n, k);
end = clock();
printf("(call splitn) count=%d times: %f \n", count, double(end - start) / CLK_TCK);
}
return 0;
}

加载更多回复(4)

69,369

社区成员

发帖
与我相关
我的任务
社区描述
C语言相关问题讨论
社区管理员
  • C语言
  • 花神庙码农
  • 架构师李肯
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧