HDU-1005

来源:互联网 发布:淘宝卖家好评的好处 编辑:程序博客网 时间:2024/06/10 01:24

此题一开始做的时候和很多人一样,同样犯了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个小时总算没有白费.

 

原创粉丝点击