HDU-1005

9 篇文章 0 订阅

此题一开始做的时候和很多人一样,同样犯了Time Exceed的错误.一开始还以为是以为自己的程序结构过水导致(使用了递归).后来改写成循环仍通不过,查阅讨论版才知道这个使用较大n的题目(1&lt;=n&lt;=100,000,000)若使用简单的循环进行解题,必然导致超时.必须发现规律后进行优化,才能解决此类问题.</

分析问题可以发现,函数中关键的一步是%7,注意到%7的值必然在0~6间循环,又由于一旦出现f(n-1)=0的情况后,f(n)便由f(n-1)唯一决定,所以一旦这个时候f(n-1)=1,则f(n)=1,所以每一个数列都是一个以0为末尾的循环,下一次一旦碰到1 1这样的序列的时候,另个循环就开始了.关键是求这里循环的周期T,并将n对这里的周期T取模.直接在前面已经求得的第一个周期中找到对应的值.问题得解.

另外发现了两个问题,

1.这里如果将T声明在main中,初始化为0,并像下面这样写,会报错:INTEGER_DEVIDE_ZERO,而若不初始化T,则会报MEMORY_ACCESS_VIOLATION这样的问题.<见代码1>



 

2.那么初始化为非零值是否可行呢?答案还是不行(= =!)我在CodeBlocks上调了很长时间,都没有发现这个问题的原因,难道说HDU OJ上的测试数据会让整个周期大于50?!但是那样的话,前面的程序也不会AC啊??这个问题我考虑了很长时间都没有答案...由于不知道OJ的测试数据,白白折腾了我2个多小时..

一直不能AC的代码1:

#include <stdio.h>
int main()
{
    int n=0,A=0,B=0,T=1;

    while(scanf("%d %d %d",&A,&B,&n),A||B||n)
    {
        int result[50]= {1,1};
        int i;
        for(i=2; i<50; i++)
        {
            result[i]=(A*result[i-1]+result[i-2]*B)%7;
            if(result[i]==1&&result[i-1]==1)
            {
                T=i-1;
		break;
            }
        }

        if(n%T)
            printf("%d\n",result[n%T-1]);
        else
            printf("%d\n",result[T-1]);
    }
    return 0;
}


AC的代码2:

#include <stdio.h>
int main()
{
    int n=0,A=0,B=0;
    //freopen(".\\1005in.txt","r",stdin);
    while(scanf("%d %d %d",&A,&B,&n),A||B||n)
    {
        int result[50]= {1,1};
        int i;
        for(i=2; i<50; i++)
        {
            result[i]=(A*result[i-1]+result[i-2]*B)%7;
            if(result[i]==1&&result[i-1]==1)
            {
                break;
            }
        }

        int T=i-1;//和代码1对比一下就知道这有多恶心了.
        //printf("T for this time:%d\n",T);
        if(n%T)
            printf("%d\n",result[n%T-1]);
        else
            printf("%d\n",result[T-1]);
    }
    return 0;
}



 

后来搜索前人的解题思路,才发现11的判断根本就是不严谨的.而且这题的测试数据有漏洞,n在非11循环的情况下,竟然能够找到对应的T,这实在令人匪夷所思.参见下面的文章:

http://hi.baidu.com/sysoj/item/20849433f73ec65b3075a1c3

http://acm.hdu.edu.cn/showproblem.php?pid=1005

已知f(1) = 1, f(2) = 1, f(n) = (A * f (n - 1) + B * f (n - 2)) % 7。

输入A,B,n,求f(n)。

一开始想直接算,果断超时。然后想,应该是个循环吧,就以第二次出现11时做标准查循环节,依然超时。后来自己调发现当A=5555,B=666666时数列是1142142142142。大汗,这种情况不是以11开始循环的,依然超时就罢了,关键是按11判断的话就错了。。

仔细想想。

因为结果一定是0到6之间的数,所以一定会循环,而且循环节不会超过49。(因为前面2个数若相同,则第三个之后的数必相同,而在49内必能找到2个相邻的数在前面出现过)。

找循环的时候实际是找第二次出现的相邻两数。

然后就可以做了。从前面几次或几十次运算中找出循环节长度、循环开始位置,把给的n划成前面循环里对应的位置。搞定。

前面的运算是这样的:先是不在循环里的部分,第二部分是第一个循环节,第二部分第二个循环节,到第三部分开头时,发现出现了和第二部分开头相同的<x,y>这时记下其位置,表示第二个循环开始的位置,到第四部分,也就是第三个循环节开头发现出现第三次的<x,y>这时用此位置减刚才的第二个循环节位置得出循环节长度。这样换算一下循环节开始位置和长度就都有了。

所以这种算法最多的运算次数不超过2个49.

#include <stdio.h> 
#include <string.h> 
 
void search(int a, int b, int n) 
{ 
    int i, x = 1, y = 1, wz, d, count = 0;//wz用于记载最先出现2次的<x,y>位置,count为了确保记第一个出现两次的<x,y> 
    int lieju[49] =       //记录序列中的前49个数,因为循环节长度一定小于49,那么最后换算出位置可以直接在这个数组中找到值。 
    { 
        0 
    }; 
    int jiyi[7][7] = //记忆jiyi[i][j]代表<i,j>出现次数 
    { 
        { 
            0 
        } 
    }; 
    lieju[0] = 1; 
    lieju[1] = 1; 
    jiyi[1][1]++; 
    if(n == 1 || n == 2) 
    { 
        printf("1\n"); 
    } 
    else 
    { 
        for(i = 3; i <= n; i++) 
        { 
            int t = x; 
            x = y; 
            y = (a * x + b * t) % 7; 
            jiyi[x][y]++; 
            if(i <= 49)lieju[i-1] = y; 
            if(jiyi[x][y] == 2) 
            { 
                count++; 
                if(count == 1)wz = i; 
            } 
            if(jiyi[x][y] == 3) 
            { 
                d = i - wz; 
                break; 
            } 
        } 
        if(i != n + 1)//如果不是直接循环出结果,就进行n的位置换算 :对循环节长度取余并考虑前面不在循环中的部分 
        { 
            wz = wz - d - 2; //wz这时用来记录前面的不在循环里的部分的长度 
            n = (n - wz) % d == 0 ? wz + d : (n - wz) % d + wz; //算n那个数最早出现在循环时对应是第几个数 
            printf("%d\n", lieju[n-1]); 
        } 
        else 
        { 
            printf("%d\n", y); 
        } 
    } 
} 
 
int main() 
{ 
    int a, b, n; 
    int i; 
    while(~scanf("%d %d %d", &a, &b, &n) && !(a == 0 && b == 0 && n == 0)) 
    { 
        search(a, b, n); 
    } 
    return 0; 
} 
 


嗯..看来我们发现了一个HDU OJ的bug..马上去报告吧! 且慢,仔细看看,真的如此么?

看看题目的条件(1<=a,b<=1000)---一记响亮的耳光甩在我的脸上!可以说明,最大的循环节就是49,而且在题目的条件限定下,AC的代码巧妙的利用了最大循环节的长度49...这不能不说是一个意外.

今晚的2个小时总算没有白费.

 

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值