第一次出题,也顺便记录一下题目
x j j xjj xjj 的计原之旅
时间限制:1000ms
空间限制:128MB
那天, x j j xjj xjj 去上计原课,老师要求他求出从 0 0 0 开始最少需要操作多少次能得到 n n n, x j j xjj xjj 感觉到很头疼,他决定找万能的 y z y yzy yzy 帮忙,可是 y z y yzy yzy 觉得这题太简单了,做这题纯粹是浪费时间,于是 x j j xjj xjj 找到了你,希望你能够帮他完成这题。
每一次操作可以将当前数字加减乘除上一个2的任意非负幂次 ( 2 0 , 2 1 , 2 2 , 2 3 , 2 4 ⋯ ⋯ 2 ∞ ) (2^0,2^1,2^2,2^3,2^4\cdots\cdots2^∞) (20,21,22,23,24⋯⋯2∞)。
输入格式
输入一个 n ( 1 ≤ n ≤ 2 31 − 1 ) n(1 \leq n \leq 2^{31}-1) n(1≤n≤231−1),表示目标数。
输出格式
输出一个正整数,表示最少的操作次数。
样例输入1
12
样例输出1
2
样例输入2
114514
样例输出2
6
样例1解释
第一次 0 + 16 = 16
第二次 16 - 4 = 12
思路1 时间复杂度 O ( l o g 2 n ) O(log_2n) O(log2n)
首先,对于一个数,从 0 0 0 得到 n n n ,与从 n n n 得到 0 0 0 无异。
每一次贪心选取大于等于 n n n 的第一个 2 x 2^x 2x ,和小于 n n n 的 2 x − 1 2^{x-1} 2x−1。
那么就有两个选择
- n = n − 2 x n = n - 2^x n=n−2x 由于会变成负数,所以可以取负,变成 n = 2 x − n n = 2^x - n n=2x−n
- n = n − 2 x − 1 n = n - 2^{x - 1} n=n−2x−1
因此只要每次贪心取这两个之中的最小值,求出让
n
n
n 变成
0
0
0 的步数
思路 1 1 1 s t d std std
#include<stdio.h>
int main() {
int n, ans = 0;
scanf("%d",&n);
while(n) {
long long x = 1;
while(x < n) x *= 2;
if(x - n < n - x / 2) n = x - n;
else n = n - x / 2;
ans++;
}
printf("%d",ans);
return 0;
}
思路2 时间复杂度 O ( l o g 2 n ) O(log_2n) O(log2n)
根据性质
性质1
100000000
-000001000
————————————
011111000
性质2
00000000 -> 00010000
00010000 -> 00000000
因此就有两个操作
- 两步生成任意个数连续的 1 1 1
- 一步可以让任意位置的 01 01 01 互换 ( 0 (0 (0 变成 1 1 1, 1 1 1 变成 0 ) 0) 0)
再考虑一种特殊情况
111101111 -> 111111111
在这种情况下就先一步将 0 0 0 转换成 1 1 1,再生成连续的一串 1 1 1
因此题解就是将 n n n 转换成二进制 01 01 01 串,判断生成每一段 1 1 1 最少需要几次
思路 2 2 2 s t d std std
#include<stdio.h>
int s[10005];
int main() {
int n;
scanf("%d",&n);
int len = 0;
s[len++] = 0;
while(n) {
if(n & 1) s[len++] = 1;
else s[len++] = 0;
n >>= 1;
}
s[len] = 0;
int ans = 0;
for(int i = 1;i < len;i++) {
if(s[i - 1] == !s[i] && s[i + 1] == !s[i]) {
s[i] = !s[i];
ans++;
}
}
int sum = 0;
for(int i = 1;i <= len;i++) {
if(s[i] == 1) sum++;
else {
if(sum == 1) ans++;
else if(sum >= 2) ans += 2;
sum = 0;
}
}
printf("%d",ans);
return 0;
}