回 帖 发 新 帖 刷新版面

主题:第37次编程比赛第1题结果

测试环境:winxp+vc6, re楼ease版本,AthLonXP2000+/768MB

第一轮 2<=n<30

3楼 euc:Wrong Answer
5楼 ITER:Wrong Answer
6楼 hyerty:Wrong Answer
7楼 Liangbch:AC 
8楼 ccpp:Wrong Answer  接口小问题,帮你改好。但结果还是……
9楼 xyhx:AC 
12楼 Liyanguestc:Wrong Answer   接口不对 只好把你的单独运行,下次留意格式。
13楼 太没劲了:Wrong Answer
14楼 hyerty:Wrong Answer
17楼 joekings:Wrong Answer
18楼 joekings:Wrong Answer
20楼 boxertony:Wrong Answer
21楼 jackin0627:Wrong Answer
22楼 xyhx:不知道为什么,运行起来是死循环,可能是我的问题,你告诉我怎么改。不过你在9楼交的很不错。


第二轮:
7楼 Liangbch:n=100 1s;n=150 23s; 不敢再测更大的。
9楼 xyhx:受限int类型,n>100结果错误。n<100速度稍逊Liangbch。



总的说明:
    从提交人数和正确率来看,这道题看起来还是有一定难度。严格的说没有人完全通过,在100以内仅有2人是正确的。有两个值错误率比较高:n=20(5+7+8) maxStep=5*7*8=280; n=21(2+3+4+5+7) maxStep=3*4*5*7=420。
    xyhx很可惜没有用到int64或者longlong。Liyanguestc作为新人还是很不错,n>100时结果正确,n=150仅需2s,可惜数据类型很投机的使用了double,n>150之后结果错误,并且n=18结果也是错误,说明算法有小问题。Liangbch的结果正确,速度不算慢,孩子生病还来捧场,太感谢了。

    这道题其实可以具体到一句话:将n分解成若干个大于1的自然数的和,使它们的最小公倍数最大。暂时没有更好的方法,只能是搜索+剪枝。剪枝策略就是尽量互质,分解成若干个质数或者质数的幂,使它们的和小于等于n(但不等于n-1,n-1特殊考虑)。剪枝充分的话,速度完全可以达到题目的要求,而且代码只需四五十行。

    作为唯一一个结果正确的人,本次比赛的[color=FF0000]冠军为Liangbch[/color]。xyhx的结果int范围也是正确的,获得优胜奖。

    Liyanguestc作为新人,表现出了非常大的潜力,剪枝做的非常好,速度很快。但可惜算法有一点漏洞,结果不是完全正确。期待下次的表现。(我下次跟你一样也是第二次参加,我就是你的对手了,加油!)

    如果对结果有任何异议,请回帖告知,我会认真复查。

回复列表 (共18个回复)

11 楼

虽然我的程序是正确的,但我并没有找到有效的剪枝算法。 xyhx, Liyanguestc能否将程序改改,提交一个正确的程序,让大家学习学习。
  楼主,如果你不忙的话,能否也写一个标准程序,让大家学习学习。

12 楼

#include<iostream>
using namespace std;

int prime[20][6],n;
__int64 max;

//获得需要的素数及素数得幂,这一段写得太丑陋了,大家包含
//分解成太大的数,显然不是最优解。n<=300,这些已经足够用了。
void getPrime()    
{
    int i,j;
    for (i=0;i<20;i++)
        for (j=0;j<6;j++)
            prime[i][j]=1011;
    prime[0][0]=2;prime[0][1]=4;prime[0][2]=8;prime[0][3]=16;prime[0][4]=32;
    prime[1][0]=3;prime[1][1]=9;prime[1][2]=27;
    prime[2][0]=5;prime[2][1]=25;
    prime[3][0]=7;
    prime[4][0]=11;
    prime[5][0]=13;
    prime[6][0]=17;
    prime[7][0]=19;
    prime[8][0]=23;
    prime[9][0]=29;
    prime[10][0]=31;
    prime[11][0]=37;
    prime[12][0]=41;
    prime[13][0]=43;
    prime[14][0]=47;
    prime[15][0]=53;
    prime[16][0]=59;
}

void Try(int last,int p,__int64 m)//搜索。仅在getprime()获得的数中间搜索,规模不大
{
    if (p<16) 
    {
        int i=0;
        while (prime[p][i]<=last)
        {
            Try(last-prime[p][i],p+1,m*prime[p][i]);
            i++;
        }
        Try(last,p+1,m);
    }
    if (last!=1 && m>max) max=m;
}

void MonkeyDance(const int n, int* maxStep)
{
    getPrime();
    max=n;
    Try(n,0,1);
    if (n>6) Try(n-6,2,6);
    *maxStep=int(max%10000);
}


这个作为参考,程序并不复杂,简单的搜索剪枝,但是考虑算法的时候要很小心。

如果程序有错请回帖告知。

13 楼

看了这次的分析,我知道我的思路是正确的。
用程序写出来就是得不到正确答案,所以不敢上交(不希望太现世了),汗啊!!!
但我还是很希望大家能帮我找找错在哪里了,请不吝赐教!!!

int main()
{
    void MonkeyDance(int n,int *maxstep);       /*求最多的步数,存在指针maxstep里*/
    void Array(int a[],int i,int n,int *maxstep);     /*求互质数序列,并存于数组a中*/
    int Multiple(int a[]);         /*求数组乘积*/
    int Compare(int a,int b);    /*比较两数是否互质*/
    int maxstep,monkeynum;
    int *maxst;
    maxst=&maxstep;
    monkeynum=200;
    MonkeyDance(monkeynum,maxst);
    printf("%d\n",maxstep);
    getch();
    return 0;
}
void MonkeyDance(int n,int *maxstep)
{
    int i,j,contain[200];   /*存互质数序列*/
    if(n<=1)
    {
        printf("error!\n");
        return;
    }
    *maxstep=n;
    if(n==2 || n==3 || n==4) return;
    for(i=0;i<200;i++) contain[i]=0;
    for(i=0,j=2;j<n/2;j++)
    {
        contain[i]=j;
        Array(contain,i,n-j,maxstep);
    }
    return;
}

void Array(int a[],int i,int n,int *maxstep)
{
    int max,j,i1,k;
    if(n==0)
    {
        max=Multiple(a);
        if(max>*maxstep)
        {
            *maxstep=max;
        }
        return;
    }
    else if(n<a[i]) return;
    else
    {
        for(j=a[i]+1;j<=n;j++)
        {
            for(i1=0;i1<=i;i1++)
            {
                if(Compare(a[i1],j)==1) continue;
                else break;
            }
            if(i1==i)
            {
                for(k=i+1;k<200;k++) a[k]=0;
                a[i+1]=j;
                Array(a,i+1,n-j,maxstep);   /*递归调用*/
            }
        }
        return;
    }
}

int Multiple(int a[])
{
    int mul=1,i;
    for(i=0;;i++)
    {
        if(a[i]!=0) mul=mul*a[i];
        else break;
    }
    return mul;
}

int Compare(int a,int b)
{
    int i,commax;
    commax=a>b?b/2:a/2;
    for(i=2;i<=commax;i++)
    {
        if(a%i==0 && b%i==0) return 0;
        else continue;
    }
    if(a%b==0 || b%a==0) return 0;
    return 1;
}


下次努力了!!!

14 楼

下次该我出题了,请楼主告诉我,如何出题,使得回复是隐藏的,如何结贴。

15 楼

[quote]下次该我出题了,请楼主告诉我,如何出题,使得回复是隐藏的,如何结贴。[/quote]
发帖的时候要在分类里选择“编程比赛”。帖子发表以后,点修改,就会多一个“结贴后才能查看回帖”的选项。在修改旁边有结贴等选项。

16 楼

[quote][quote]回1楼,分解后的若干个数,当最小公倍数最大,它们不一定互质。比如21,最好的分解是2+3+4+5+7,而不是互质的情况。[/quote]

所以只能尽量。因为最后计算乘积的话,合数因子会对其他的质数因子产生影响,例如2、3、4、5、7中因为2是4的因子,因此2不能算,只能算4。

我的想法是,当对一个数N进行分解的时候,选择一个接近N/2而且与其他数不重复的质数,记为Z(2/N),另外一个数当然就是R&nbsp;=&nbsp;N&nbsp;-&nbsp;Z(2/N),Z(2/N)当然不会是其他数的因子,反之亦然,因此接下来测试R:

如果R是质数,且唯一,那么这种分解就成功了,如果不唯一,则失败;

如果存在一个其它的质数数Zt,使得Zt是R的因子,那么去掉Zt后分解成功;

如果存在一个其它的合数Ot,使得Ot是R的因子或者R是Ot的因子,就看N是否为质数,如果是,就不分解。如果不是,就保留R和Ot的大者,去掉因子。如果不存在其他的数与R有以上关系,则继续下一轮分解。还有一点就是如果Z*R<=N,则必须继续选取其他的Z和R。并且R不能为1。

例如对于21,
第一轮:
Z(2/N)=11,R=10,分解成功
(11、10)

第二轮:
对于11分解为Z=5,R=6,成功;
对于10分解为Z=5,R=5,质数5重复,因此继续选择Z和R。Z=3,R=7,成功
(5,6,3,7)

第三轮:
对于5分解为Z=2,R=3,由于质数3重复,所以5不能分解
对于6分解为Z=3,R=3,由于质数3重复,所以继续选择Z和R。Z=2,R=4,考虑到2是4的因子,因此去掉2,保留4。
对于3无法分解。
对于7分解为Z=3,R=4,4重复,而N=7为质数,因此不能分解
(5,[2],4,3,7)

第四轮分解
对于5,不能分解
对于2、3、7也不能分解
对于4,分解为Z=2、R=2,质数2重复,不能分解
(5,[2],4,3,7)为最终结果,其中[2]被去掉,不参与相乘。


[/quote]
跟我的分法是接近呵呵

17 楼

在9楼提交的没有改为long int 我用的是VC觉得应该没问题,^o^

在22楼提交的实在是对不起,由于时间匆忙,提交后我自己都运行不了,后来发现是有两句语句忘写了。正确的是下面:
#include<malloc.h>
void MonkeyDance(const int no, int* maxStep)
{    
    int i,count,num;
    int *mon,*str;//指向数组
    int *pback,*pforw;//指向数组元素
    long int temp,max=0,mid=1,t;
    long int gcd(long int a,long int b);   
    long int ComNum(int *mark,int count);
    for(i=2,count=0,num=0;num+i<=no;i++,count++)
        num+=i;
    num-=(i-1);
    str=mon=(int * )calloc(count+1,sizeof(int));
    if(mon==NULL)
        printf("Allocat memory failed!\n");
    else
    {
        for(i=0;i<count-1;i++)
            *mon++=i+2;
        *mon=no-num;
        if(count==1)        
            max=str[0];    
        else
        {
            pback=str+count-2;pforw=pback+1;
            do
            {
                temp=(*pback)+(*pforw);
                if( temp>=((*pback)+2)*3)
                {
                    (*pback)++;
                    while(1)
                    {
                        if(temp>=(*pback+1)*3)
                        {
                            temp-=*pback;
                            *pforw=(*pback)+1;
                            pback++;pforw++;
                            count++;
                        }
                        else
                        {
                            *pforw=temp-(*pback);                            
                            break;
                        }
                    }
                }
                   mid=ComNum(str,count-2);
                while(1)
                {
                    if( *pback<*pforw )
                    {
                        t=(*pback)*(*pforw)/gcd(*pback,*pforw);
                        t=mid*t/gcd(mid,t);
                        max=t>max?t:max;
                        [color=FF0000](*pback)++;(*pforw)--;[/color]                    }
                    else
                    {                                
                        *pback+=(*pforw);
                        if(pback!=str)                        
                            pback--;
                        pforw--;                        
                        count--;                    
                        break;
                    }
                }            

            }while(pforw!=str);
        }
    }
    max=str[0]>max?str[0]:max;
    *maxStep=max%10000;    
}
void Print(int *addr,int n)
{
    int i;
    for(i=0;i<n;i++)
        printf("%d ",*addr++);
    putchar('\n');
}
long int gcd(long int a,long int b)
{
    if (a%b==0) return b;
    return gcd(b,a%b);
}
long int ComNum(int *mark,int count)
{
    int i;
    long int res=1;
    for(i=0;i<count;i++)
        res=mark[i]*res/gcd(mark[i],res);
    return res;        
}

18 楼

恩 程序结果正确。但是速度没有大的提升。

我来回复

您尚未登录,请登录后再回复。点此登录或注册