!codeforces 558C Amr and Chemistry-yy题-(位运算相关)

题意:有n个数,每次进行的操作只能是除以2或者乘以2,求这n个数转换成同一个数字所需要的最小的操作步数

分析:

乍一看题目,觉得好难,对于这种每次有两种情况求最后到达的终点的balabala的我就觉得很复杂,这道题说明其实并不可怕,至少有一部分并不可怕。

这道题的做法是暴力枚举出每个数能够走到的所有的数,记录步数,最后找交点输出最小值即可。找交点也不要想复杂了,这n个数都能到达的数就是交点,那么只需要用一个数组记录能到到达这个点的起点个数,最后起点个数等于n的就是交点,这在以每个起点出发枚举的时候就能维护。cnt[i]维护能到点 i 的起点个数,vis[i]维护所有起点到达点 i 的步数和。

说运算相关只是把数写成二进制用左移右移来看这样直观些,其实不用这个也行。最大的交点最多是输入的最大值mx,因为如果要去往大于mx的点必须经过mx这个点那么与最小的步骤数矛盾。

所以解题过程是:扫描每个起点,先一直右移到mx,再回到这个起点开始左移,左移前检查这个点是奇数还是偶数,如果是偶数,继续左移;如果是奇数,那么左移后再右移到mx,然后继续左移。也就是在左移过程中只要遇到一个节点是奇数,就要叉开一条路,让这个奇数先除2再不断的乘以2,但是原本的左移路线一直不变,只是多了一些路线而已,如果不能理解画一下就明白了。

这题还有一些小trick:

1.cnt[a[i]]不能预处理为1,因为有可能有多个起点是一样的即:a[i]=a[j],那么cnt[a[i]]就不应该是1而是2,这个错误是偶然发现的;

2.数组cnt[]和vis[]要开2*10^5,虽然我们推出来终点的极限是输入的最大值mx,mx<10^5,但是由于在算的过程中可能会算到大于mx的数(尽管这对于答案没有贡献),所以数组只开10^5的话,就小了,所以就WA了

代码:

#include<iostream>
#include<cstring>
#define max(a,b) a>b?a:b
#define min(a,b) a<b?a:b
#define INF 1000000007
using namespace std;
int n,a[1000010];
int vis[1000010],cnt[1000010];
int mi,mx;
int main()
{
	cin>>n;
	mx=-1,mi=INF;
	memset(vis,0,sizeof(vis));
	memset(cnt,0,sizeof(cnt));
	for(int i=0;i<n;i++){
		cin>>a[i];
		mx=max(mx,a[i]);
	}
//	for(int i=0;i<n;i++) cnt[a[i]]=1;  //错误,不能这么初始化 
	for(int i=0;i<n;i++){
		cnt[a[i]]++;
		int tmp=a[i];
		int tot=0;
		while(tmp<=mx){
			tmp*=2;
			tot++;
			cnt[tmp]++;
			vis[tmp]+=tot;
		}
		tmp=a[i];
		tot=0;
		while(tmp>1){			
			if((tmp%2==1)&&(tmp!=1)){
				int tmp2=tmp/2;
				int step=tot+1;
				while(tmp2<=mx){
					tmp2*=2;
					step++;
					cnt[tmp2]++;
					vis[tmp2]+=step;
				}
			}
			tmp/=2;
			tot++;
			cnt[tmp]++;
			vis[tmp]+=tot;
		}		
	}
	for(int i=1;i<=mx;i++)
		if(cnt[i]==n) mi=min(mi,vis[i]);
	cout<<mi<<endl;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值