Getting Zero(思维 + lowbit)
题意:
给一个数 n n n ,两种操作: n + 1 n+1 n+1 或 n ∗ 2 n*2 n∗2 ,求使 n % 327681 = 0 n \ \% \ 327681 =0 n % 327681=0 的最小操作数
思路:
327681 = 2 15 327681 = 2 ^ {15} 327681=215, 那么对于 n % 2 15 = 0 n \ \% \ 2 ^ {15} = 0 n % 215=0 我们就可以这样思考:因为是对 2 15 2 ^ {15} 215 这样一个 2 2 2 的幂次的数,那么我们就可以考虑这样一个操作在二进制表示上是否有特殊性。
显然,在二进制上对一个数 n % 2 15 n \ \% \ 2 ^ {15} n % 215 这样一个操作,可以看做只保留 n n n 在二进制上的前 15 15 15 位,即 2 0 − 2 14 2 ^ {0} - 2 ^ {14} 20−214 位上的数。
那么在考虑两种操作在二进制上的意义:
- n + 1 n + 1 n+1 就是在最后一位上加 1 1 1 ,但是会存在进位的后续问题
- n ∗ 2 n * 2 n∗2 就是将原 n n n 二进制上的所有数向前移动一位,比如 n = 1011 n = 1011 n=1011 ,那么 n ∗ 2 = 10110 n * 2 = 10110 n∗2=10110
那我们最终要使 n % 2 15 = 0 n \ \% \ 2 ^ {15} = 0 n % 215=0,其实就是使 n n n 的前 15 15 15 位都为 0 0 0,明显最简单的方法就是不断重复 n ∗ 2 n * 2 n∗2,这样只要将 n n n 的最低位的 1 1 1 移动到 15 15 15 位之后,就可以了。
那么考虑最坏的情况: n % 2 = 1 n \ \% \ 2 = 1 n % 2=1,即最低位在第一位,那么就需要移动 15 15 15 次,即操作 15 15 15 次。
接下来考虑 n + 1 n + 1 n+1 操作,该操作的价值在于将 n n n 的最低位通过进位的方式提升。比如 $ n = 111$,通过 n + 1 = 1000 n + 1 = 1000 n+1=1000,最低位的 1 1 1 从第一位提升到了第四位,那么原来需要操作 15 15 15 次就减少到了 15 − 4 + 1 = 12 15 - 4 + 1 = 12 15−4+1=12 次,成功压缩了操作次数,使答案更优。
而且考虑到 n + 1 n + 1 n+1 这一操作肯定是先操作的价值更高的。还是上面的例子: n = 111 n = 111 n=111,若是先进行 n ∗ 2 = 1110 n * 2 = 1110 n∗2=1110 操作,那么在进行 n + 1 n + 1 n+1 操作就需要更多次来使最低位的 1 1 1 提升。 n + 1 = 1111 , n + 1 = 10000 n + 1 = 1111, n + 1 = 10000 n+1=1111,n+1=10000。
所以在最优解中 n + 1 n + 1 n+1 一定是开始就进行操作了的。那么我们就可以暴力遍历:考虑到 a n s ≤ 15 ans \leq 15 ans≤15 (因为只进行 n ∗ 2 n * 2 n∗2 最差也是 15 15 15),所以 n + 1 n + 1 n+1 的操作最多也是进行 15 15 15 ,多了就一定不是最优解。
那么我们就可以遍历操作,假设 n + i ( i ≤ 15 ) n + i(i \leq 15) n+i(i≤15),再计算对 n + i n + i n+i 需要进行多少 ( j ) (j) (j) 次 n ∗ 2 n * 2 n∗2 操作才能满足题目要求,然后 a n s i j = i + j ans_{ij} = i + j ansij=i+j,取最小的 a n s i j ans _ {ij} ansij即可。
最后补充对 j j j 的求解,设 l o w low low 为 n n n 最低位的 1 1 1 的下标,那么 j = 15 − l o w j = 15 - low j=15−low,而 l o w low low 可以采用 l o w b i t lowbit lowbit 算法: l o w = n & − n low = n \ \& -n low=n &−n,其中 − n -n −n 为 n n n 在二进制上取负数,就等于 n n n 的补集加 1 1 1 ,如 n = 10010 n = 10010 n=10010, 那么 n n n 的补集就为 01101 01101 01101,则 − n = 01101 + 1 = 01110 -n = 01101 + 1 = 01110 −n=01101+1=01110,则 & \& & 一下就是第二位的 1 1 1, n & − n = 00010 = 2 1 n \ \& -n = 00010 = 2 ^ {1} n &−n=00010=21。
#include<bits/stdc++.h>
#define lazy ios::sync_with_stdio(0);cin.tie(0); cout.tie(0);
#define inf 0x3f3f3f3f3f3f3f
#define endl '\n'
using namespace std;
typedef long long ll;
const double eps = 1e-10;
const ll N = 2e5 + 7;
const ll M = 1e9 + 7;
const ll mod = 998244353;
ll t, a[20];
int main() {
lazy;
a[0] = 1;
for (int i = 1; i < 20; i++) {// 预处理出2的幂次
a[i] = a[i - 1] * 2;
}
cin >> t;
while (t--) {
ll n;
cin >> n;
n %= a[15];// 放置n本就比327681大
if (n == 0) cout << 0 << " ";// 特判0
else {
ll mn = 15;
n--;
for (int i = 0; i <= 15; i++) {// 遍历 n + i
n++;
ll low = n & -n;// 取最小位
for (int i = 0; i <= 16; i++) {// 将最小位转化为位数,比如将327681转化为2的15次,把幂次数保留
if (low == a[i]) {
low = i;
break;
}
}
mn = min(mn, i + 15 - low);// 对答案取小
}
cout << mn << " ";
}
}
}