如何验证问题答案的正确性

languagec 2006-08-18 09:13:10
前面有位同事出了一道题,是这样的.
有1000个苹果,10个筐,每个筐可以装任意个苹果,但是苹果装好以后筐就不可以再被拆出来了.那么现在就有一个问题,这1000个苹果如何装到10个筐中,才能在需要任意个苹果(1000以内)的时候都可以得到?
我知道有个答案是正确的,就是1 2 4 8 16 32 64 128 256 489
但是一个学生给了另外一组答案1 2 4 8 16 32 62 125 250 500
我想知道她的答案是不是也是正确的,如果写个程序判断一下的话,是不是可以呢?该怎么写?
...全文
459 29 打赏 收藏 转发到动态 举报
写回复
用AI写文章
29 条回复
切换为时间正序
请发表友善的回复…
发表回复
languagec 2006-08-21
  • 打赏
  • 举报
回复
疯狂懒猪 真是厉害厉害
crazy_lazy_pig 2006-08-21
  • 打赏
  • 举报
回复
再补充一点:
an >= Sn - 2^(n-1) + 1
可能出现负数,但是这关系不大,还有一个条件——{an}是递增数列且min{an}=1——可以约束着an至少是1(当然在n不是很大的时候,即n<Sn时,an的值取 1 也是不满足条件的)。
crazy_lazy_pig 2006-08-21
  • 打赏
  • 举报
回复
怎么我也帖错了点东西。上面第二条应该为:an <= S_(n-1) + 1(S_(n-1)是数列前n-1项和)

上面的条件还是要求和运算,比较麻烦,进一步改进,把条件(2)里的S_(n-1)转化成Sn, 把条件(3)里的Sn转化成an,则:
an <= (Sn + 1)/2
an >= Sn - 2^(n-1) + 1
因为S_(n-1) = Sn - an,所以从an 开始判断直到 a1, 就可以了,这样少了连加的实现。

另外,根据上述条件写出找到所有合法拆分方法的程序也是不困难的(用递归很容易实现,非递算法也是容易找到的)。因为我这里没有C的编译器,代码也就不写了,有兴趣的可以做做。
crazy_lazy_pig 2006-08-21
  • 打赏
  • 举报
回复
哈哈,尾巴的证明不错,看上去很严谨。我得到了一个跟尾巴的等价条件,计算量会小一些:

数列 {an} 是一个递增数列,且满足以下条件:
(1) min{an}=1 (尾巴的第一条件的改进,以满足筐数大于总苹果数的情况)
(2) an <= Sn - 1 (尾巴的第二条件,其中Sn为数列前n项和)
(3) Sn <= 2^n - 1 (尾巴条件的略微改进,对于楼主的问题而言,S10 = 1000)
则对于 1 到 Sn的任意整数都可以由 {an} 的一个子列的和来表示。
(具体证明这里就不再详细叙述了,也是利用数学归纳法,思路跟尾巴的基本相同)
languagec 2006-08-20
  • 打赏
  • 举报
回复
呵 我在想是不是有更快的办法,除了穷举之外.
tailzhou 2006-08-20
  • 打赏
  • 举报
回复
复杂度有点不一样。
我的总共只找一次组合;

你的好象对0-n间的每一个i都找一次组合;

anyue417 2006-08-20
  • 打赏
  • 举报
回复
大家还真肯动脑筋
fly4free 2006-08-20
  • 打赏
  • 举报
回复
to 尾巴:
“举例:
62(+2) 你为了得到64用到了2,那么66就不能再用64+2了;
而1 2 4 8 16 32 64 128 256 489 序列的66是用64+2组成的;”

66可以用62+4啊
xddzccn 2006-08-20
  • 打赏
  • 举报
回复
写了个会跑死机器的垃圾代码,幸亏学校的机器还不像
想象的垃圾,也只用1秒钟,呵呵!
现在是用最笨的方法,回去在想想,有时间能用数学方法最好!
#include <stdio.h>
main()
{

int a[10]={1,2,4,8,16,32,62,125,250,500};

unsigned int c=1;

int i;

int j;

int sum=0;

int n=1;

while(n<=1000)
{

for(c=1; c<1024; c++)
{
sum=0;

for(j=0; j<10; j++)
{

sum=sum+a[j]*((c>>j)&(0x01)) ;

}


if(sum==n)
{

printf("%d\n",n);

n++ ;

break;

}
}






}

if(n==1001)printf("OK");

}
tailzhou 2006-08-20
  • 打赏
  • 举报
回复
假设10个数字分别为num[i] 1<=i<=10;

条件:
一:第1个数只能是1;
二.第n个数<=(1到n-1个数的和)+1;
三.10个数的和==1000

只要能证明满足以上3个条件的序列中,1到sum(num[1]...num[k])之间的数都能用num[1]...num[k]组合出来;
那么,满足此三条件的序列,一定满足题目要求;

归纳法证明:
1). 当k=1或2时,明显成立;

2). 假设k=m时成立,即1到sum(num[1]...num[m])之间的数都可以用num[1]到num[m]来组合;

3). 现需要证明k=m+1时成立;
现将1到sum(num[1]...num[m+1])之间的数分成三个区域:

a).1到 num[m+1](不包含num[m+1])区域,
因为 num[m+1]<= sum(num[1]...num[m])+1,

所以该区间是 1到sum(num[1]...num[m])区间 的子集 直接得出;

b).num[m+1];
直接得出;

c).num[m+1]+1到sum(num[1]...num[m+1])区域,
明显该区域与 1到sum(num[1]...num[m]) 区域有一一对应关系:
num[m+1]+1到sum(num[1]...num[m+1])区域内的数都是 1到sum(num[1]...num[m])区域内对应位置的数加上num[m+1];
所以该区域也成立;

4.结论:满足此3个条件的序列一定满足题目的要求;

接着证明满足题目条件的序列一定满足此三条件;
使用反正法:
一:第1个数只能是1;
如果第一个数可以不是1,那么明显1不能用这10个数组成;
二.第n个数<=(1到n-1个数的和)+1;
如果num[n]可以大于sum(num[1]...num[n-1]),那么sum(num[1]...num[n-1])+1这个数肯定不能用这10个数组成;
三.10个数的和==1000
如果10个数的和!=1000,那么1000肯定不能用这10个数组成;

由此可以得出结论,此3条件 与题目的条件 是等价的!!


依照该证明,可以写出不需要枚举的伪码程序如下:
#define NUM_LENGTH 10
int num[NUM_LENGTH ];
int sum;
......输入10个从小到大的数到数组num,如果不是有序的数列,可以先对该10个数排序;

if (num[0]==1)
{
sum=1;
for (i=2;i<10;++i)
{
if (num[i]>sum+1) goto printno;
sum+=num[i];
}
if (sum==1000)
{
printf("yes!\n");
return 0;
}
}
printno:;
printf("no!\n");
return 0;
zh_OA 2006-08-20
  • 打赏
  • 举报
回复
说话太罗嗦,又连续三回帖了,poor
tailzhou 2006-08-20
  • 打赏
  • 举报
回复
还是贴错:
二.第n个数<=(1到n-1个数的和)+1;
如果num[n]可以大于sum(num[1]...num[n-1])+1,那么sum(num[1]...num[n-1])+1这个数肯定不能用这10个数组成;

依照该证明,可以写出不需要枚举的伪码程序如下:
#define NUM_LENGTH 10
int num[NUM_LENGTH ];
int sum;
......输入10个从小到大的数到数组num,如果不是有序的数列,可以先对该10个数排序;

if (num[0]==1)
{
sum=1;
for (i=2;i<10;++i)
{
if (num[i]>sum+1) break;
sum+=num[i];
}
if (sum==1000)
{
printf("yes!\n");
return 0;
}
}

printf("no!\n");
return 0;


tailzhou 2006-08-20
  • 打赏
  • 举报
回复
二.第n个数<=(1到n-1个数的和)+1;
如果num[n]可以大于sum(num[1]...num[n-1])+1,那么sum(num[1]...num[n-1])+2这个数肯定不能用这10个数组成;
tailzhou 2006-08-20
  • 打赏
  • 举报
回复
假设10个数字分别为num[i] 1<=i<=10;

条件:
一:第1个数只能是1;
二.第n个数<=(1到n-1个数的和)+1;
三.10个数的和==1000

只要能证明满足以上3个条件的序列中,1到sum(num[1]...num[k])之间的数都能用num[1]...num[k]组合出来;
那么,满足此三条件的序列,一定满足题目要求;

归纳法证明:
1). 当k=1或2时,明显成立;

2). 假设k=m时成立,即0到sum(num[0]...num[m])之间的数都可以用num[1]到num[m]来组合;

3). 现需要证明k=m+1时成立;
现将0到sum(num[0]...num[m+1])之间的数分成两个区域:

a).0到 num[m+1]区域,
因为 num[m+1]< sum(num[0]...num[m]),
所以该区间的数可以由2)直接得出:都可以用num[1]到num[m]来组合;成立;

b).num[m+1]到sum(num[0]...num[m+1])区域,
明显该区域与 0到sum(num[0]...num[m]) 区域有一一对应关系:
num[m+1]到sum(num[0]...num[m+1])区域内的数都是 0到sum(num[0]...num[m])区域内对应位置的数加上num[m+1];
所以该区域也成立;

4.结论:满足此3个条件的序列一定满足题目的要求;

接着证明满足题目条件的序列一定满足此三条件;
使用反正法:
一:第1个数只能是1;
如果第一个数可以不是1,那么明显1不能用这10个数组成;
二.第n个数<=(1到n-1个数的和)+1;
如果num[n]可以大于sum(num[1]...num[n-1]),那么sum(num[1]...num[n-1])+1这个数肯定不能用这10个数组成;
三.10个数的和==1000
如果10个数的和!=1000,那么1000肯定不能用这10个数组成;

由此可以得出结论,此3条件 与题目的条件 是等价的!!

languagec 2006-08-19
  • 打赏
  • 举报
回复
#include "stdio.h"
#include "stdlib.h"
#include "string.h"

int RESULT,FIND;
int stack[20],top=0;
int NUM[]={1,2,4,8,16,32,64,125,250,498};

void push(int data)
{
stack[top++]=data;
}

void pop()
{
top--;
}

void print()
{
int i;
for(i=0;i<top;i++)
printf("%d ",NUM[ stack[i]-1 ]);
printf("\n\n");
}

int check(int sum)
{
int temp=0;
int i;
for(i=0;i<top;i++)
{
temp+=NUM[ stack[i]-1 ];
}
return temp==sum;
}

void Travel(int c,int n,int m)
{
int cur;
if(top>=m)
{
if(check(RESULT))
{
print(); FIND=1;
}
return ;
}
for(cur=c+1;cur<=n;cur++)
{
push(cur);
Travel(cur,n,m);
pop();
}
}

int main()
{
int n,m;
int i;
n=10;
for(i=1;i<=1000;i++)
{

RESULT=i;
FIND=0;
for(m=1;m<=10;m++)
{
top=0;

Travel(0,n,m);

}
if(FIND==0)
{
printf("the answer is wrong \n");
return 0;
}

}
printf("the answer is right\n");

return 0;
}


这是我自己写的,复杂度和尾巴的一样,只是代码简洁性没有尾巴好.
我也是找出所有的组合.
crazy_lazy_pig 2006-08-19
  • 打赏
  • 举报
回复
第二条是怎么推出来的?
tailzhou 2006-08-19
  • 打赏
  • 举报
回复
有什么好点的办法把所有可能的拆分法都找出来?

这样的拆分法的数量应该不少;


假设序列是有序的(即使不有序,排序后也不会影响结果);

则:

一:第1个数只能是1;

二.第n个数<=(1到n-1个数的和)+1;

三.10个数的和==1000



tailzhou 2006-08-19
  • 打赏
  • 举报
回复
给个更完整的例子:
(1).1 2 4 8 16 32 64 128 256 489
==>
(2).1 2 4 8 16 32 62(+2) 125(+1+2) 250(+2+4) 500
==>
(3).1 2 4 8 14(+2) 28(+4) 62 125 250 506

明显61用第(3)组序列是组合不出的;




crazy_lazy_pig 2006-08-19
  • 打赏
  • 举报
回复
有什么好点的办法把所有可能的拆分法都找出来?
tailzhou 2006-08-19
  • 打赏
  • 举报
回复
即使
1 2 4 8 16 32 62 125 250 500 ==》
1 2 4 8 16 32 62(+2) 125(+1+2) 250(+2+4) 500
成立,也不能得出1 2 4 8 16 32 62 125 250 500 可以的.

举例:
62(+2) 你为了得到64用到了2,那么66就不能再用64+2了;
而1 2 4 8 16 32 64 128 256 489 序列的66是用64+2组成的;

加载更多回复(9)

69,373

社区成员

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

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