• Codeforces 558C Amr and Chemistry 全都变相等


    

    题意:给定一个数列,每次操作仅仅能将某个数乘以2或者除以2(向下取整)。

    求最小的操作次数使得全部的数都变为同样值。







    比赛的时候最后没实现。唉。之后才A掉。開始一直在想二分次数,可是半天想不出怎么推断。后来发现事实上每一个数都能变成的数非常少非常少(最多400个不到)。于是想到用数学方法+一点暴力,可惜时间不够了。

    也不能全然算是数论题。仅仅是用到了一些数学思想。须要一点预处理,后面的计算中还会用到二分的技巧。

    对某个数n,设n = s*2^r(当中s为奇数),则n能变成这样一些数:s*2^k(k = 0,1,2 ...... )以及s能变成的更小的数。s能变成的数有s/2。设s/2 = s'*2^r',则还能变成s'*2^k(k = 0。1,2 ...... )以及s‘能变成的更小的数 ...... 以此类推下去。于是我们能够注意到这样一个情况:假设一个数a能变成数n。那么数a一定能够变成数s。反之。若一个数不能变成数s。那么它一定不能变成数n。进一步,假设两个数a,b都能变成n。那么a和b一定都能变成s。所以,我们能够先求出这个s,即全部的数都能变成s。注意,由于s是奇数,所以每一个数变成s仅仅能是不断除以2(向下取整)的结果。并且,我们至少能够求出一个这种s出来——s = 1。

    这是显然的。所以为了使操作次数更少。我们取s中最大的(当然并不一定最优)。可是显然,全部的数必须都要能够变成这个数,那是肯定的。也就是说,大于这个数的奇数,都无法变到了。

    既然这样。那么能够作为最后都变成的候选的数仅仅能是s以及s*2^k(k = 0。1,2 ...... )于是我们枚举k的值,计算每一个数变成s*2^k须要的次数再相加。更新最小值就可以。

    求s有点类似于求全部的数的“最大公约数”这种意思。仅仅是这里是求x和y都能变到的最大的奇数而已。也是用递归求:假设x比y的两倍大,那么x/=2,假设y比x的两倍大,那么y/=2。否则x和y同一时候除以2,直到x和y相等。即得到了共同的能变成的最大的奇数。

    s攻克了。第二个问题是怎样高速求一个数变成还有一个数所须要的次数。

    这里用了一点小技巧,能够直接预处理后以近乎O(1)的复杂度求出。

    首先定义一个数组base[i] 、base[i]满足i = base[i]*2^r(事实上basi[i]就是上面所说的s),显然。对于奇数有base[i] = i,对于偶数有base[i] = base[i/2]。

    接下来就是计算cal(i, j) = 将 i 变成 j 须要的步数。

    能够发现,仅仅要将 i 不断除以2直到base[i] = base[j]为止,此时的 i 和 j 仅仅是差了2的幂的倍数,那么还须要操作这个幂值的次数就可以将 i 变为 j。

    而这个幂值就等于log2(max(i,j)/min(i,j))。于是我们仅仅须要求将 i 变为一个base值等于base[j]的数最少要除以多少次2。

    当然直接算也能够,就不断除以2直到base值等于base[j]为止。可是外层已经用去了n*log(n)复杂度,于是这里我们再对除以2的次数进行2分。一个数最大10W,最多也就能除以16次2。所以二分的复杂度是log2(16) = 4差点儿就等于O(1)了。能进行二分是由于,若 i 数除以2^k后得到base值比base[j]更小。那么除以大于2^k的值一定更不会得到base[i]了。反之。i 除以2^k后得到的base值若比base[j]大,则它还要继续除以2。

    具有单调性,所以能够二分。于是cal(i,j) = 二分出来的幂值+(int)log2(max(i, j)/min(i, j));












    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    #include <cmath>
    #include <cstring>
    #include <string>
    #include <stack>
    #include <queue>
    #include <vector>
    #include <map>
    using namespace std;
    
    const int MAX = 100005;
    const int INF = MAX*20;
    int base[MAX];
    
    int n, a[MAX];
    
    void initial() //预处理base数组
    {
        for(int i = 1; i <= 100000; i++)
        {
            if(i%2)
                base[i] = i;
            else
                base[i] = base[i/2];
        }
    }
    
    int getbase(int x, int y) //递归求两个数能同一时候变成的最大的奇数
    {
        if(x == y)
            return x;
        if(x >> 1 >= y)
            return getbase(x >> 1, y);
        if(x << 1 <= y)
            return getbase(x, y >> 1);
        return getbase(x >> 1, y >> 1);
    }
    
    int cal(int x, int y) //求将x变成y所须要的步数
    {
        int l = 0, r = 20, mid;
        while(l < r)
        {
            mid = (l + r) >> 1;
            if(base[x >> mid] > base[y])
                l = mid + 1;
            else
                r = mid;
        }
        x >>= l;
        return l + (int)log2(max(x, y)/min(x, y));
    }
    
    void input()
    {
        for(int i = 0; i < n; i++)
            scanf("%d", &a[i]);
    }
    
    void solve()
    {
        int all_base = a[0];
        for(int i = 1; i < n; i++)
            all_base = getbase(all_base, a[i]); //先求出全部数都能变成的最大奇数all_base
        int ans = INF, number = all_base;
        while(number <= 100000) //枚举all_base的倍数作为最后变成的数,计算并更新
        {
            int sum = 0;
            for(int i = 0; i < n; i++)
                sum += cal(a[i], number);
            ans = min(ans, sum);
            number <<= 1;
        }
        printf("%d
    ", ans);
    }
    
    int main()
    {
        initial();
        while(scanf("%d", &n) != EOF)
        {
            input();
            solve();
        }
        return 0;
    }
    

    

  • 相关阅读:
    模仿jquery的一些实现
    使按钮失效的方法
    类似jquery的一个demo
    oracle 集合变量以及自定义异常的用法
    java的for循环问题的解决,以及安卓中ListView插入数据的问题
    Spring AOP基于xml配置实例
    plsql 的循环之 goto
    Spring AOP报错
    补全aaz288 可能有问题的过程 P_COMPL_AAZ288
    Spring注解配置
  • 原文地址:https://www.cnblogs.com/clnchanpin/p/7029351.html
Copyright © 2020-2023  润新知