《奇偶》——一道小思维题

题目

时间限制:1s   内存限制:256MB

夏老师有一个包含 n 个正整数的数组 a,他可以对该数组做任意次操作,每次操作时,他可以选取数组中的一个元素a[i]并进行如下操作:

  • 若a[i]为奇数,则将a[i]变为a[i]×2。
  • 若a[i]为偶数,则将a[i]变为a[i]/2。

夏老师希望做完若干上述操作后,使得数组a中最大值与最小值的差最小,请问这个最小值是多少呢?

输入数据

第一行一个整数n。

第二行有n个整数,用单个空格隔开,表示数组a中的各个元素。

输出数据

一个整数,表示若干上述操作后,数组a中最大值与最小值最小差值。

输入样例

5
2 1 8 2 3

输出样例

1

说明

对于50%的数据,1≤n≤10,对于100%的数据,1≤n≤1000,1≤a[i]≤10^{5}

心路历程

当我在写这道题的时候我脑子里当时涌现出几种想法。第一种是设法找出一种最优的处理方法让它算完以后一定是最小的差值,最后很遗憾,没找到;第二种是去枚举所有情况,或者是找到一种有效的枚举方法(排除不必要的枚举),这个其实和正解是一致的,但当时没有坚持去想(也没有想到,叻~);第三种是我想的时间最长的一种方法,就是考虑到a中的每个数可以变化出的数都是有限的,我想把这些数给分别列出来,然后试图去找一种最优的挑选方法,得到答案,不过最后最优的挑选方法没有挑出来,然后我想到了一个超时的暴力解法,就是去枚举一个可能出现的最小值,然后在a中每个数的变化范围中去挑离这个最小值最近的(且比它大)的值作为挑选结果,最后统计答案。

正解思路

我们先贪心地考虑一下,这题无外乎让我们最大化最小值和最小化最大值。我们可以一步一步来。经过考虑,我们发现最小化最大值不是很容易,毕竟对于一个偶数(奇数不能变小了),能除好多次呢。但是最大化最小值是个不难的操作,我们只要将读入的每一个奇数乘2即可(事实上也只能乘一个2),这样一来所有的数就都达到了它们能取到的最大值了。

接下来是最小化最大值,这个我们采用暴力方法,即如果最大值能变小,就将它变小,当不能变小(即为奇数)时停止,并在整个过程中不断的更新答案。

最后我们再来解释一下为什么这样做是对的,我们的最小值永远是最大的,这个需要细品你。在最大值还没有变小的时候,最小值已经被我们调成了最大的情况。而在后面减小最大值的过程中,我们可能将最大值除以2并将其转化成了最小值。注意这个时候最小值仍然是“最大的”,原因是如果通过其他数字去产生最小值一定要比该最小值要小。这样我们解释了“最小值永远是最大的”这个命题。接下来其实我们做的其实是枚举了所有可能的最大值,当这个过程结束之后,我们当然可以得到正确答案。

#include<bits/stdc++.h>
using namespace std;

int n,a[1001],ans;

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]),a[i]=a[i]&1?a[i]*2:a[i];
    sort(a+1,a+n+1);
    ans=a[n]-a[1];
    while(1)
    {
        if(a[n]%2==0) a[n]/=2;
        else break;
        sort(a+1,a+n+1);
        ans=min(a[n]-a[1],ans);
    }
    printf("%d\n",ans);
 //   system("pause");
    return 0;
}

当然可能有的人会担心这样写会超时,因为这个while循环最多可能执行nloga[i]次,每次循环里的快排又会有nlogn,所以总共就会是n^{2}log nloga[i], 这个的话已经有大约165,528,094次了,外加可能有的一些常数,有点虚~

为了解决这个问题,我们可以考虑用一些数据结构去优化。因为这题每个循环只用到了最大值和最小值,所以我们得找一个能维护序列的这些信息的东东,multiset是个不错的选择。这样我们就可以把每层循环的复杂度降至logn了,这样总的时间复杂度O(nloga[i]logn)。

#include<bits/stdc++.h>
using namespace std;

int n,a[1001],ans;
multiset<int>s;

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]),a[i]=a[i]&1?a[i]*2:a[i],s.insert(a[i]);
    ans=*(--s.end())-*(s.begin());
   // cout<<ans<<endl;
    while(1)
    {
    //	for(multiset<int>::iterator it=s.begin();it!=s.end();it++) cout<<*it<<' ';
    //	cout<<' '<<ans<<endl;
    	int da=*(--s.end());
        if(da&1) break;
        s.erase(da); 
		s.insert(da/2);
        ans=min(*(--s.end())-*(s.begin()),ans);
    }
    printf("%d\n",ans);
 //   system("pause");
    return 0;
}

可以看一下两次的时间对比:

🆗结束,respect~

 

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值