zoukankan      html  css  js  c++  java
  • Bits Reverse(贪心、位运算)

    题意

    给定两个非负整数(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;
    }
    
  • 相关阅读:
    6th Alpha阶段的postmortem报告
    两种open()函数
    文件操作权限第一位的0是什么意思
    vi的查找与替换
    浅析Linux下的/etc/profile、/etc/bashrc、~/.bash_profile、~/.bashrc文件
    Linux命令总结:file
    Linux下/boot目录
    英语单词简记
    Linux下grep命令
    Linux下find命令
  • 原文地址:https://www.cnblogs.com/miraclepbc/p/14380617.html
Copyright © 2011-2022 走看看