11 solutions

  • 7
    @ 2022-3-26 16:08:56

    首先是一个不那么正经的解法

    我们考虑到这题的数据只有 1024,那么,我们考虑小学时老师教你的:

    你有 2 个苹果,然后你又拿到了 3 个苹果,最后你有 5 个苹果,所以 2+3=52 + 3 = 5 那加法就完成了(代码中的 ushortunsigned short

    short $(ushort x, ushort y) {
    	return std::string().append(x, '.').append(y, '.').length();
    }
    

    那么减法呢?我们知道,xy=x+(y)x - y = x + \left( -y \right),在计算机中,y=y+1-y = \sim y + 1,so,xy=x+y+1x - y = x + \sim y + 1,调用之前写的加法就好:

    short _(short x, short y) {
    	y = ~y;
    	return $($(x, reinterpret_cast<ushort&>(y)), 1);
    }
    

    最后乘法,还是考虑小学的时候老师教你的:

    x×yx \times y 就是 yyxx 相加。

    还是用 string。我们往一个空的 string 里面塞 yy 次长度为 xx 的字符串,然后就好了。
    但是我们发现一个问题:我们既不能 for (int i = 0;i < y;i++),也不能 while (y--)。这可怎么办呢?还是用 string。我们每循环一次就往一个初始为空的字符串里塞一个字符,这个串有多长我们就循环了多少次。然后,乘法也被淦掉了:

    int X(short x, short y) {
    	std::string str = "";
    	std::string tt = std::string().append(x, ' ');
    	std::string res = "";
    	while (str.append(1, ' ').length() <= y) {
    		res.append(tt);
    	}
    	return res.length();
    }
    

    最后是这种做法的完整代码:

    #include <iostream>
    #include <string>
    #define ushort unsigned short
    short $(ushort x, ushort y) {
    	return std::string().append(x, '.').append(y, '.').length();
    }
    short _(short x, short y) {
    	y = ~y;
    	return $($(x, reinterpret_cast<ushort&>(y)), 1);
    }
    int X(short x, short y) {
    	std::string str = "";
    	std::string tt = std::string().append(x, ' ');
    	std::string res = "";
    	while (str.append(1, ' ').length() <= y) {
    		res.append(tt);
    	}
    	return res.length();
    }
    int main() {
    	int x, y;
    	std::cin >> x >> y;
    	std::cout << $(x, y) << ' ' << _(x, y) << ' ' << X(x, y) << std::endl;
    }
    

    玩够了不着调的瞎搞做法,咱回过头来瞅瞅如何用位运算解题。(三年后的补档)

    正经的解法

    加法

    咱先研究俩一位数相加的情况,有:

    • 0 + 0 = 00
    • 1 + 0 = 01
    • 0 + 1 = 01
    • 1 + 1 = 10

    注意到:xxyy 相加,高位为 xandyx \operatorname{and} y,低位为 xxoryx \operatorname{xor} y

    然后咱考虑进位,有:

    • 0 + 0 + 1 = 00 + 1 = 01
    • 1 + 0 + 1 = 01 + 1 = 10
    • 0 + 1 + 1 = 01 + 1 = 10
    • 1 + 1 + 1 = 10 + 1 = 11

    发现实际上进位实际上可以作为一单独过程处理,而不影响先前的结论。(事实上全加器就是可以由多个半加器拼合而成的)

    现在拓展到任意二进制数,以 7 + 14 为例:

        0  1  1  1 (7)
     +  1  1  1  0 (14)
     --------------
    [1][1][1][0]   (carry bits)
     --------------
     1  0  1  0  1 (21)
    

    显然要是像咱平时列竖式那样一位一位整有点麻烦。(其实也不麻烦)

    但基于我们之前的结论:进位可以作为一个单独过程处理。有啥用呢?我们将进位与异或分离处理,则:

         0  1  1  1 (7)
      +  1  1  1  0 (14)
      --------------
         1  0  0  1 (9)
    c:0  1  1  0    (12)
      --------------
      0  0  1  0  1 (5)
    c:1  0  0       (16)
      --------------
      1  0  1  0  1 (21)
    c:0  0          (0)
    

    其中每一次变换的第一行都是上一组的两个值按位异或的结果;第二行(carry)为上一组两个值按位与并左移一位的结果。注意到:

    • 每一组数的和不变;这保证结果正确
    • 进位(carry)数的有效位数变短;这保证进位最终变为 0(程序能够结束)

    部分代码:

    short add(ushort x, ushort y) {
        while (y != 0) {
            int r = x & y;
            x ^= y;
            y = r << 1;
        }
        return x;
    }
    

    (实际上这种方式最差需要 O(log(n))\mathcal{O}(\log(n)),与按位单独计算相同,但比咱之前瞎搞做法的 O(n)\mathcal{O}(n) 好)

    减法

    利用补码,原理相同,不再赘述

    部分代码:

    short sub(short x, short y) {
        y = ~y;
        return add(add(x, reinterpret_cast<ushort&>(y)), 1);
    }
    

    乘法

    显而易见的有一种想法就是,x×yx \times y 就是 yyxx 相加。这种方式 yy 次相加效率较低。既然正经做法,咱想想是否有更快的方式呢?

    可以从快速幂中借鉴思路。快速幂解决的是 r=nkr = n^k,即 kknn 相乘的问题。其核心是将指数 kk 分解:

    k=n=02nbn\displaystyle k = \sum_{n=0}^\infty 2^n b_n

    得到结果 rr

    r=n=0a2nbn\displaystyle r = \prod_{n=0}^{\infty} a^{2^n b_n}

    其中 a2nbna^{2^n b_n} 可以写作 (a2n)bn\left(a^{2^n}\right)^{b_n},其中 a2n=(a2n1)2a^{2^n} = \left(a^{2^{n-1}}\right)^2 可在 O(1)\mathcal{O}(1) 时间内得到,而 bnb_n 仅有 0(使结果便为 1)与 1(保持结果不变)两种情况,从而实现快速

    而咱所要解决的乘法是 r=nkr = n^k,即 kknn 相乘的问题。同样将 kk 分解后,所要得到的结果 rr

    $\displaystyle r = \sum_{n=0}^{\infty} a \times {2^n b_n}$

    相似的,a×2nbna \times {2^n b_n} 可以写作 (2na)bn\left(2^n a\right)b_n,其中 2na=(2n1a)×22^n a = \left(2^{n-1}a\right) \times 2 可在 O(1)\mathcal{O}(1) 时间内得到,而 bnb_n 仅有 0(使结果便为 0)与 1(保持结果不变)两种情况。你问乘 2 咋整?左移。

    部分代码:

    int mul(short x, short y) {
        int res = 0;
        while (y != 0) {
            if ((y & 1) == 1) {
                res = add(x, res);
            }
            x <<= 1;
            y >>= 1;
        }
        return res;
    }
    

    最终代码实现:

    #include <iostream>
    #include <string>
    typedef unsigned short ushort;
    short add(ushort x, ushort y) {
        while (y != 0) {
            int r = x & y;
            x ^= y;
            y = r << 1;
        }
        return x;
    }
    short sub(short x, short y) {
        y = ~y;
        return add(add(x, reinterpret_cast<ushort&>(y)), 1);
    }
    int mul(short x, short y) {
        int res = 0;
        while (y != 0) {
            if ((y & 1) == 1) {
                res = add(x, res);
            }
            x <<= 1;
            y >>= 1;
        }
        return res;
    }
    int main() {
        int x, y;
        std::cin >> x >> y;
        std::cout << add(x, y) << ' ' << sub(x, y) << ' ' << mul(x, y) << std::endl;
    }
    

    Information

    ID
    246
    Time
    1000ms
    Memory
    256MiB
    Difficulty
    2
    Tags
    (None)
    # Submissions
    339
    Accepted
    77
    Uploaded By