题目链接:http://codeforces.com/problemset/problem/558/C
题意:给出n个数,让你通过下面两种操作,把它们转换为同一个数。求最少的操作数。
1.ai = ai*2
2.ai = ai/2,向下取整
思路:
可以除以二 或者 乘以二,就相当于位运算的右移和左移。用两个数组,vis 数组, cnt 数组。刚开始都初始化为0; vis[i] 表示 i 这个数可以由几个数转化而来,cnt[i] 表示题目给出的 n 个数全部转化为 i 需要的操作数。
首先遍历数组找到 ai 的最大值记为 MAX,那么所有数转化的上界就是 MAX,因为如果最终转化的数如果大于MAX,那么所有值都要转化为大于MAX的那个数,很明显这不是最后的答案。
把一个数表示为二进制数,
1、如果最低位是0,那么这个数右移一位(除以2),再左移一位(乘以2),就得到原来的数
2、如果最低位是1,那么这个数右移一位(除以2),再左移一位(乘以2),得不到原来的数
那么:
3要转化为8,最少需要4步操作,先除以2,再乘以2,再乘以2,再乘以2
2要转化为8,最少需要2步操作,先乘以2,再乘以2
处理一个数 ai:
1、对 ai 执行左移操作,记录 ai 通过左移能够得到的数字,上限为MAX,vis 数组加1,cnt数组记录步数
2、对 ai 执行右移操作,直到 ai 为0
若 ai 的最低位为1,右移一步之后,进行左移,上限为MAX,维持vis数组和cnt数组
若 ai 的最低位为0,右移一步,维持vis数组和cnt数组
这样我们就把一个数的所有情况都处理出来了,并且是最少的操作数。
最后遍历数组找 vis[i] 为n,并且操作数最小。
#include <iostream> #include <cstring> #include <cstdlib> #include <cstdio> #include <math.h> #include <algorithm> #include <queue> using namespace std; #define INF 0x3f3f3f3f #define met(a,b) memset(a,b,sizeof(a)) #define ll long long #define N 100010 int m,vis[N*2];///有多少数通过乘以2或除以2能到i int te[N*2];///统计到i需要多少步 int a[N*2]; void add(int s) { vis[s]++; int a=s,b=s; int h=0,k=0; while(a<=m)///往上乘不能超过最大值 { a=a<<1; h++; vis[a]++; te[a]+=h; } while(b) { if(b&1 && b!=1)///如果二进制末尾是1除以2再乘以2不是原来的数 { b=b>>1; k++; vis[b]++; te[b]+=k; int k1=k; int d=b; while(d<=m) { d=d<<1; k1++; vis[d]++; te[d]+=k1; } } else { b=b>>1; k++; vis[b]++; te[b]+=k; } } } int main() { int n; scanf("%d",&n); for(int i=0;i<n;i++) { scanf("%d",&a[i]); m=max(m,a[i]); } met(vis,0);met(te,0); for(int i=0;i<n;i++) add(a[i]); int ans=INF; for(int i=1;i<N*2;i++) { if(vis[i]==n)///需要所有的数都能到i ans=min(ans,te[i]);///取最小步数 } printf("%d\n",ans); return 0; }