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);
}