这段C++代码和这段Python代码有什么区别?

1 投票
2 回答
553 浏览
提问于 2025-04-18 16:11

回答

感谢 @TheDark 发现了溢出的问题。新的 C++ 解决方案也挺搞笑的,实在是太冗余了:

if(2*i > n && 2*i > i)

替换了旧的代码行 if(2*i > n)

背景

我正在做 HackerRank 上的这个问题,不过这个问题可能和我提问的内容不完全相关。如果你看不到网页,或者不想注册账号,下面有问题的简单描述。

问题

我的 C++ 代码超时了,但我的 Python 代码没有。我最开始怀疑是因为溢出,但我用 sizeof 确认过 unsigned long long 可以达到 2^64 - 1,这是问题的上限。

我几乎是直接把我的 C++ 代码翻译成 Python,想看看是不是我的算法导致了超时,但让我惊讶的是,我的 Python 代码通过了所有测试用例。

C++ 代码:

#include <iostream>

bool pot(unsigned long long n)
{
    if (n % 2 == 0) return pot(n/2);
    return (n==1); // returns true if n is power of two
}

unsigned long long gpt(unsigned long long n)
{
    unsigned long long i = 1;
    while(2*i < n) {
        i *= 2;
    }
    return i; // returns greatest power of two less than n
}

int main()
{    
    unsigned int t;
    std::cin >> t;
    std::cout << sizeof(unsigned long long) << std::endl;
    for(unsigned int i = 0; i < t; i++)
    {
        unsigned long long n;
        unsigned long long count = 1;
        std::cin >> n;
        while(n > 1) {
            if (pot(n)) n /= 2;
            else n -= gpt(n);
            count++;
        }
        if (count % 2 == 0) std::cout << "Louise" << std::endl;
        else std::cout << "Richard" << std::endl;
    }
}

Python 2.7 代码:

def pot(n):
    while n % 2 == 0:
        n/=2
    return n==1

def gpt(n):
    i = 1
    while 2*i < n:
        i *= 2
    return i

t = int(raw_input())
for i in range(t):
    n = int(raw_input())
    count = 1
    while n != 1:
        if pot(n):
            n /= 2
        else:
            n -= gpt(n)
        count += 1
    if count % 2 == 0:
        print "Louise"
    else:
        print "Richard"

在我看来,这两种版本看起来一模一样。我还是觉得我可能被某种方式欺骗了,实际上我的 C++ 代码中发生了溢出,导致了超时。

问题描述

路易斯和理查德在玩一个游戏。他们有一个计数器,初始值为 N。路易斯先出手,之后轮流进行。在游戏中,他们执行以下操作。

如果 N 不是 2 的幂,他们就把计数器减去小于 N 的最大 2 的幂。

如果 N 是 2 的幂,他们就把计数器减去 N 的一半。

得到的新值就是新的 N,继续用于后续操作。

游戏在计数器减到 1 时结束,也就是 N == 1,最后一个进行有效操作的人获胜。

给定 N,你的任务是找出游戏的赢家。

输入格式

第一行包含一个整数 T,表示测试用例的数量。接下来的 T 行中,每行包含 N,即计数器的初始值。

约束条件

1 ≤ T ≤ 10

1 ≤ N ≤ 2^64 - 1

输出格式

对于每个测试用例,打印赢家的名字,每个名字占一行。如果路易斯赢了游戏,打印 "Louise"。否则,打印 "Richard"。(引号只是为了说明)

示例输入

1

6

示例输出

Richard

解释

因为 6 不是 2 的幂,路易斯减去小于 6 的最大 2 的幂,即 4,因此计数器减到 2。

因为 2 是 2 的幂,理查德将计数器减去 2 的一半,即 1。因此计数器减到 1。

当我们达到结束条件 N == 1 时,理查德赢得了游戏。

2 个回答

0

试试这个位运算的小技巧,可能会稍微快一点:

unsigned long largest_power_of_two_not_greater_than(unsigned long x) {
  for (unsigned long y; (y = x & (x - 1)); x = y) {}
  return x;
}

x&(x-1) 的意思是把 x 中最右边的一个1去掉。所以当 x 变成2的幂时,y 就会变成0,这样循环就结束了。这个2的幂是小于或等于原来的 x 的最大2的幂。循环的次数和 x 中1的个数有关,平均来说,这个方法的循环次数只有你方法的一半。而且,这个方法不会出现溢出的问题。(不过如果原来的 x 是0,它会返回0。这可能是你想要的,也可能不是。)

需要注意的是,如果原来的 x 是2的幂,函数会直接返回这个值。所以这个函数也可以用来判断 x 是否是2的幂(或者是0)。

虽然这个方法挺有趣的,但在实际代码中,你可能更应该找找你编译器里有没有和这个 gcc 内置函数相似的东西(除非你的编译器就是 gcc,那这里就是了):

  • 内置函数:int __builtin_clz (unsigned int x) 返回 X 中从最高位开始的前导0的个数。如果 X 是0,结果是未定义的。

(对于 unsigned long 参数,还有 __builtin_clzl;对于 unsigned long long 参数,还有 __builtin_clzll。)

4

n 大于 2 的 63 次方时,你的 gpt 函数最终会让 i 达到 2 的 63 次方,然后再把 2 的 63 次方乘以 2,这样就会出现溢出,结果变成 0。接下来就会陷入一个无限循环,每次都在把 0 乘以 2。

撰写回答