关于进制的几道题

来源:互联网 发布:lnmp一键 安装php扩展 编辑:程序博客网 时间:2024/06/16 06:45

1.给出一堆数,其中一个数出现了1次,其他的所有的数出现了2次。求出现了一次的数。

这个就是很简单的异或操作了。

#include <cstdio>using namespace std;int main(void){    long long ans,a;    int N;    while(~scanf("%d", &N)){        ans = 0;        for(int i = 0; i < N; ++i){            scanf("%lld",&a);            ans ^= a;        }        printf("%lld\n",ans);    }    return 0;}

2.给出一堆数,其中一个数出现了1次。其他所有的数出现了3次。求出现一次的数是几。

根据上面的结论,我们很容易类比得到三进制异或,但是三进制异或是怎么样的呢?

我们可以考虑一下二进制异或的本质:对于多个数的异或,我们可以这样看:将每个数进行二进制表达分解,对于每一位,我们分别统计1出现的次数,如果出现了奇数次,则在最后的结果中,否则就是该位就不对最后的结果有贡献。

所以,这里利用二进制异或的本质:是在统计,每个数字二进制表达中,每一位1出现的总次数。

所以,由此我们可以想到,可以对于10进制的每一位,统计0-9每个数字出现的次数。出现为1次的数字才会出现在最后的结果中。

#include<stdio.h> #include<string.h> int main() {      int n,i,j;      while(~scanf("%d",&n)){          char s[20];          int cnt[20][10]={0};          while(n--){              scanf("%s",s);              int Len=strlen(s);              for(i=Len-1;i>=0;i--)                  cnt[Len-1-i][s[i]-'0']++;          }          for(i=19;i>=0;i--)              for(j=0;j<=9;j++)                  if(cnt[i][j]%3)                      putchar(j+'0');          putchar('\n');      }      return 0;  } 

需要注意的一点是:为了加快速度,这里是把数字按照字符串读入的。

我们也可以用位运算来进行上面的统计。

#include <cstdio>#include <algorithm> using namespace std; int main(void){    int N;    while(~scanf("%d", &N)){        long long a;        long long a1;//保存出现一次数字的异或和        long long a2;//保存出现两次的数字异或和        long long a3;//没有出现三次数字的异或和        a1 = a2 = 0;        for(int i = 0; i < N; ++i){            scanf("%lld",&a);            a2 |= a1&a;//更新出现两次的            a1 ^= a;//更新出现一次的,同时会抹掉第一位    //下面处理进位            a3 = ~(a1&a2);//找出没有出现的            a1 &= a3;//从第一位抹掉            a2 &= a3;//从第二位抹掉        }        printf("%lld\n",a1);    }    return 0;}
这个虽然也是O(n)的算法,但是这个常数还是很大的。  

这样,我们就完美解决了这个问题。

3.给出一堆数,其中一个数字出现了Y次,其他的数字出现了X次(Y<X),求给出出现Y次数的数字。

其实,看完上面的两个问题的讨论,我们就会有想法了。

算法一:和上面一个问题的算法一一样,我们依然统计每个位上,每个数字出现的次数。最后直接暴力查找即可。

算法二:利用位运算来进行统计。

              我们注意到,我们依然还是用二进制的异或来模拟X进制的异或。所以,对于进位操作,我们只有加1的情况下,才可能进位。

#include <cstdio>#include <cstring>using namespace std;long long cnt[20];int main(void){    int N;    int X = 3;    int Y = 1;    long long a;    while(~scanf("%d", &N)){        memset(cnt,0,sizeof(long long)*(X+1));        for(int i = 0; i < N; ++i){            scanf("%lld",&a);            cnt[2] |= cnt[1]&a;            cnt[1] ^= a;            for(int j = 3; j <= X; ++j){                cnt[j] |= cnt[j-1]&cnt[1];                long long up = ~ (cnt[j-1]&cnt[1]);                cnt[j-1] &= up;                cnt[1] &= up;            }        }        printf("%lld\n",cnt[Y]);    }    return 0;}


4.现在有一个函数random(),他会等概率的产生0-M的所有整数。现在要你利用这个函数,来设计另外一个函数rand(N),他会等概率的产生0-N的所有整数。

如果直接利用区间的伸缩变换,random()*N/M,这样会产生锯齿,0-M所有数出现的概率是不一样的。

我们可以将N表示成M进制的数,对于每一位我们进行随机。这样得到的数就是在base上是个等概率的,同时对于每个m长度内也是等概率的。

但是我们要将其变成在0-N的内等概率的,就需要考虑向n摄入。

<pre name="code" class="cpp">int rand(int n){    int base=1,ret=0;    while(base<n)        ret+=Random()*base,base*=m;    return ret<base/n*n?ret%n:rand(n);}



0 0