Interview_C++_day27

通过位运算实现加减乘除取模

  • 加法操作

对于每一位而言,在不考虑进位的情况下,可以得到
\[ 0+0 = 0\\ 0+1 = 1\\ 1+0 = 1\\ 1+1 = 0 \]
显然,上面的情况符合 异或 操作且只有第四种情况发生了进位,进位情况符合 操作。在所有发生进位处,应该在更高的一位处加一,这个值可以通过 左移 操作实现。那么就可以得到
\[ x+y = x \oplus y + (x \& y)<<1 \]
可以发现,后面的式子变成了一个新的加法式,那么只要递归计算即可。当 \((x \& y)<<1 == 0\) 时,就消除了加法式。

ll add(ll x, ll y) {
    ll newx = x^y;
    ll newy = (x&y)<<1;
    if(newy == 0)   return newx;
    return add(newx, newy);
}
  • 减法操作

减法操作可以看成
\[ \begin{aligned} -y &= \sim y+1\\ x+y & = x+(-y)\\ & = x + \sim y+1 \end{aligned} \]
同样可以通过加法式得到

ll sub(ll x, ll y) {
    return add(x, add(~y, 1));
}
  • 乘法操作

假设 \(y=1010\),则可以关注于二进制上的 \(1\) 位,那么可以将 \(x*y\) 做出拆解
\[ \begin{aligned} x*y &= x*1010\\ &= x*1000 + x*0010 \end{aligned} \]
而这个当乘数只有一个 \(1\) 时,可以通过二进制的左移操作实现。

ll mul(ll x, ll y) {
    ll ans = 0;
    int flag = x^y;
    x = x<0 ? add(~x, 1) : x;
    y = y<0 ? add(~y, 1) : y;
    for(int i=0; (1ll<<i)<=y; i++) {
        if(y&(1ll<<i)) {
            ans = add(ans, x<<i);
        }
    }
    return flag<0 ? add(~ans, 1) : ans;
}
  • 除法操作

和乘法操作思想一样,枚举答案每一位是否为 \(1\),通过左移来得到乘积并减去。先从大的开始找,如果有一位是 \(1\),那么就在答案将这一位设成 \(1\)。

ll divide(ll x, ll y) {
    ll ans = 0;
    int flag = x^y;
    x = x<0 ? add(~x, 1) : x;
    y = y<0 ? add(~y, 1) : y;
    for(int i=31; i>=0; i--) {
        if(x >= (1ll*y<<i)) {
            ans |= (1ll<<i);
            x = sub(x, 1ll*y<<i);
        }
    }
    return flag<0 ? add(~ans, 1) : ans;
}
  • 取模操作

已经得到了除法的结果,那么取模操作也很容易实现了

ll mod(ll x, ll y) {
    x = x<0 ? add(~x, 1) : x;
    y = y<0 ? add(~y, 1) : y;
    return sub(x, mul(y, divide(x, y)));
}

为什么子类对象可以赋值给父类对象而反过来却不行

  • 子类继承于父类,它含有父类的部分,又做了扩充。如果子类对象赋值给父类变量,则使用该变量只能访问子类的父类部分。
  • 如果反过来,这个子类变量如果去访问它的扩充成员变量,就会访问不到,造成内存越界。
上一篇:看懂IPv6+,这篇就够了


下一篇:在项目中导入MRC的文件时解决办法