校OJ 1038贪吃松鼠的两种解法

来源:互联网 发布:合工大网络视频课 编辑:程序博客网 时间:2024/04/29 22:54


1038: 贪吃的松鼠

Time Limit: 3 Sec  Memory Limit: 2 MB
Submit: 140  Solved: 14
[Submit][Status][Web Board]

Description

冬天到了,n只松鼠决定一起采集一波坚果过冬,可是在松鼠老大清点的时候,发现少了一些食物,于是召开松鼠大会,知道是有一只小松鼠偷吃了一部分食物!可是他们只知道每一个松鼠运了m个,但是某一个只松鼠运了k个,请找出这个松鼠!(本题时限3s 限制内存2M) 

Input

输入有多组数据,每组第一行包含三个数nmkn<=100000,1<m<=9,k<m)。
然后输入m*(n-1)+k个数,第i个数表示运送第i个坚果的松鼠编号,编号范围在2^30以内。 

Output

输出这个松鼠的编号 

Sample Input

3 3 2
1 2 1 2 1 2 3 3

Sample Output

3

首先,欢迎观看我写的第一篇博客。作为一个ACMer第一篇博客就用来祭祀我们自己本校的题了。^_^

 

题意:一共有n只松鼠他们每个人本来需要搬运m个坚果,但是,其中有一只松鼠在搬运过程中偷吃了一个所以它所搬运的坚果数量不足k要求我们找出这只偷吃坚果的松鼠(也就是只搬运了k个坚果的松鼠)

 

首先这个题的运行要求。时间要求是3s,空间也不过是2M。我们如果注意到这个以后就会明白如果我们真的像题目中所说的那样真的开一个长度为100000的数组使用下标法的话先不说用代码如何实现,我们可以确定开到这个大小以后内存就已经不够了。而且它给的时间也并不富裕。所以,我在这里使用了一种把一个数字分别存储到一个10*10的二维数组num中。

接下来就是对二维数组num 的解释和应用。

这样一来实际上这个方法与下表法没有差异,只是把存储方法上做了改变。对于上表中num[j][k]所表示的意思是在第j位上出现数字k的次数。(此处的k与前面的k没有关系。)在最后的统计中查看1~10位上出现数字0~9的次数。如果某位上某个数字出现的次数不能被m所整除则这一位上这个数字是异常的。(在这里最好是直接从高位开始往低位寻找,这样一来直接输出异常数组即可不必先存入一个数字中。)

我先举个例子:

假设在没偷吃的n-1只松鼠中只有a只松鼠的第三位上数字是‘3’,则这在num[3][3]上的值是a*m,假设在没偷吃的n-1只松鼠中只有b只松鼠的编号的第5位上的数字是‘8’,同时那只偷吃的松鼠的第5位上的数字也是‘8’,则num[5][8]的值是b*m+k,当我们查到num[5][8]时会发现num[5][8]的值不能被m整除所以第5位上有8属于异常。

接下来就是由我自己的方法写的代码:

#include<stdio.h> int num[10][10];int main(){int n,m,k;while(scanf("%d%d%d",&n,&m,&k)!=EOF){long long l=m*(n-1)+k;int a;for(int j=0;j<l;j++){scanf("%d",&a);int count=0;while(a>0){num[count][a%10]++;a/=10;count++;}   /*******************用于查错的备注****************************** for(int j=0;j<10;j++)for(int k=0;k<10;k++)可以去掉备注查询每次输入一个数据后 {在num二维表中各个元素的值即在第j位 cout<<num[j][k];上出现数字k的次数。 }cout<<endl;***********************************************************/}for(int j=9;j>=0;j--)        //因为没偷吃的松鼠它搬运的坚果的数量是m, { //偷吃的松鼠搬运的坚果的数量是k(k<m如果for(int k=0;k<10;k++)    //k>=m怎么能算是偷吃能^-^)所以对于偷吃 { //的松鼠的编号有关的数字必然不能被m所整除。 if(num[j][k]%m!=0){printf("%d",k);}}}printf("\n"); }} 

    用这种方法并不适合,所有的数据记录。只适合在这种题中使用。(要求找出编号出现次数异常的某个编号,不过最后要求我们输出的是所有的编号,或者找出所有数字中出现次数重复的数字就不适用这种方法。例如:校OJ中  重复的数字 就必须要用正常的下标法。)
    本来到这里也应该结束了,但是,做完后我就纳闷这个题为什么是进制转化进阶,所以,就问一个朋友拿到了他使用二进制做的代码。
下面是我朋友使用二进制知识完成的代码。
#include<stdio.h>#include<string.h>int a[35];int power(int a,int b){    int res = 1;    for(int i = 0;i < b;++i)        res *= a;    return res;}int main(){    int n,m,k;    while(scanf("%d%d%d",&n,&m,&k)!=EOF){        memset(a,0,sizeof(a));        for(int i = 0;i < m*(n-1)+k;++i){            int num;            scanf("%d",&num);            for(int j = 0;j < 31;++j){                if(num&(1 << j))   //这里使用的位运算把1左移i位。                    a[j] ++;       //具体的有关位运算的细节希望大                a[j] %= m;   //家能上网弄弄清楚所有位运算。            }   //因为位运算的速度一般的加减法要更快         }        int ans = 0;        for(int j = 0;j < 31;++j)            if(a[j] != 0)                ans += power(2,j);  //这一步就是找出二进制中1出现        printf("%d\n",ans);//位置的异常并转化成十进制中。     }    return 0;}
    在这个代码中其实思路的本质上和我的思路上相同的,只是我们数据的存储拆成十位存储到一个10*10的二维数组中,而它存储到一个32*1的一维数组中,因为转化成二进制后,每一位上的值只有‘1’或者‘0’。所以,是转化成二进制后每一位上的数组是否是1并且记录。
    另外两个代码中我的代码虽然数组开的空间比较大,但是,在运算时避开了耗时较多的2的次幂运算所以我的代码的时间还是比我朋友的更好,第一个是我朋友的代码的提
交结果第二个的结果是我的代码的提交结果。虽然如此,其实我朋友的代码有改进空间来降低时间上过长的问题。这个就给大家自己思考了。^_^

下面是写给那些看过位运算的人的:

     异或虽然很好而且运算时间也快,但是,位运算的异或运算只能解决那些出现次数有奇数偶数之分的编号或者数字,但在这个题中无法使用异或运算来解决,因为在题目中如果m是偶数,k也是偶数时时无法解出答案的。其实位运算也很重要,下面给大家推荐一个使用异或运算来解开的题:http://acdream.info/problem?pid=1096

   
0 0