关于进制的几道题

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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值