题意
给定两个非负整数(x)和(y)。
规定一种操作,逆序任意三个相邻的二进制位。
问最少需要多少次操作,能使得(x = y)。若不能达到,则输出(-1)。
数据范围
(1 leq T leq 10000)
(0 leq x, y leq 10^{18})
思路
我们先观察一下这个逆序操作有什么性质,假设三个相邻的二进制位构成一个三元组((a, b, c)),逆序之后变为了((c, b, a))。
可以发现,中间的二进制数不变,两端的数字交换。因此一个很自然的思路就是将(x)和(y)分别转换成二进制形式,然后奇偶分别考虑。
另一个性质是,任意奇数位之间都可以交换,任意偶数位之间也可以交换。
另外需要考虑的一点是,最后使得(x = y),我们可以假定(y)不变,令(x)变向(y)。因为(y)的一次操作对应(x)的一次操作。
第一问,能不能达到(x = y),这个可以通过分别计算(x)和(y)中奇数位(1)的个数是否相等,偶数位(1)的个数是否相等来判断。
第二问,最少需要多少步,这个需要思考一个贪心策略。以奇数位为例进行思考(偶数位同理),从低位到高位,将(x)与(y)每一位进行比对。若第(j)个奇数位不同,则往高位寻找第一个满足下列条件奇数位(k),有(x_k = y_j),且(k > j),然后交换(x)的第(j)个奇数位和第(k)个奇数位,这需要(k - j)步。
这里给出一个简短的证明,假定(1 dots j - 1)个奇数位(x)与(y)相同,而第(j)个奇数位不同,那么我们必然需要从高位找一个奇数位(k)来交换到第(j)位,假定用(k')时步数最少,需要(k' - j)步。而,按照之前的策略,将(k)位交换(j)位,然后我们可以再将交换后的(k)位(即原来的(j)位)与(k')位交换,这样总步数也是(k' - j)步。这说明我们的策略不差于最优策略。得证!
这种做法的时间复杂度是(O(34^2T)),时间是允许的,但是考虑一个优化。我们可以先预处理出(x)和(y)的所有奇数位中(1)的位置(偶数位同理),计算(sum (x_i - y_i)),正确性显然。这样时间复杂度是(O(34T))。
代码
- 不加优化版本
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 100;
int a[N], b[N];
int cnt1, cnt2;
int main()
{
int T;
scanf("%d", &T);
for(int i = 1; i <= T; i ++) {
ll num1, num2;
scanf("%lld%lld", &num1, &num2);
memset(a, 0, sizeof(a));
memset(b, 0, sizeof(b));
for(int j = 0; j < 100; j ++) {
a[j] = num1 % 2;
num1 /= 2;
}
for(int j = 0; j < 100; j ++) {
b[j] = num2 % 2;
num2 /= 2;
}
int ans = 0;
cnt1 = cnt2 = 0;
for(int j = 0; j < 100; j += 2) {
if(a[j]) cnt1 ++;
if(b[j]) cnt2 ++;
}
if(cnt1 != cnt2) {
printf("Case %d: -1
", i);
continue;
}
for(int j = 0; j < 100; j += 2) {
if(a[j] == b[j]) continue;
for(int k = j + 2; k < 100; k += 2) {
if(a[k] == b[j]) {
swap(a[k], a[j]);
ans += (k - j) / 2;
break;
}
}
}
cnt1 = cnt2 = 0;
for(int j = 1; j < 100; j += 2) {
if(a[j]) cnt1 ++;
if(b[j]) cnt2 ++;
}
if(cnt1 != cnt2) {
printf("Case %d: -1
", i);
continue;
}
for(int j = 1; j < 100; j += 2) {
if(a[j] == b[j]) continue;
for(int k = j + 2; k < 100; k += 2) {
if(a[k] == b[j]) {
swap(a[k], a[j]);
ans += (k - j) / 2;
break;
}
}
}
printf("Case %d: %d
", i, ans);
}
return 0;
}
- 优化版本
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 100;
int a[N], b[N];
int cnt1, cnt2;
int loc1[N], loc2[N];
int main()
{
int T;
scanf("%d", &T);
for(int i = 1; i <= T; i ++) {
ll num1, num2;
scanf("%lld%lld", &num1, &num2);
memset(a, 0, sizeof(a));
memset(b, 0, sizeof(b));
for(int j = 0; j < 100; j ++) {
a[j] = num1 % 2;
num1 /= 2;
}
for(int j = 0; j < 100; j ++) {
b[j] = num2 % 2;
num2 /= 2;
}
int ans = 0;
cnt1 = cnt2 = 0;
for(int j = 0; j < 100; j += 2) {
if(a[j]) loc1[cnt1] = j, cnt1 ++;
if(b[j]) loc2[cnt2] = j, cnt2 ++;
}
if(cnt1 != cnt2) {
printf("Case %d: -1
", i);
continue;
}
for(int j = 0; j < cnt1; j ++) ans += abs(loc1[j] - loc2[j]) / 2;
cnt1 = cnt2 = 0;
for(int j = 1; j < 100; j += 2) {
if(a[j]) loc1[cnt1] = j, cnt1 ++;
if(b[j]) loc2[cnt2] = j, cnt2 ++;
}
if(cnt1 != cnt2) {
printf("Case %d: -1
", i);
continue;
}
for(int j = 0; j < cnt1; j ++) ans += abs(loc1[j] - loc2[j]) / 2;
printf("Case %d: %d
", i, ans);
}
return 0;
}