1.给出一堆数,其中一个数出现了1次,其他的所有的数出现了2次。求出现了一次的数。
这个就是很简单的异或操作了。
#include
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
#include
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
#include
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进制的异或。所以,对于进位操作,我们只有加1的情况下,才可能进位。 如果直接利用区间的伸缩变换,random()*N/M,这样会产生锯齿,0-M所有数出现的概率是不一样的。 我们可以将N表示成M进制的数,对于每一位我们进行随机。这样得到的数就是在base上是个等概率的,同时对于每个m长度内也是等概率的。 但是我们要将其变成在0-N的内等概率的,就需要考虑向n摄入。 #include
4.现在有一个函数random(),他会等概率的产生0-M的所有整数。现在要你利用这个函数,来设计另外一个函数rand(N),他会等概率的产生0-N的所有整数。int rand(int n)
{
int base=1,ret=0;
while(base