CometOJ #10 沉鱼落雁 | 思维

题意:给你n个数字,其中有的数字会重复出现(但最多出现3次),问你如何安排这些数字,可以使得相同数字之间的最小间隔最大。不需要打印具体的安排策略,只要求出最大的最小间隔即可。

这里首先要搞明白,最小间隔指的是什么。比如说3 4 5 3 6 3 4,这里的最小间隔就是1(题目规定间隔为j - i - 1),3的间隔有2,1两种,4的讲个是4 。但是要求的是最小间隔,所以取这些间隔中的最小值2 。

经过思考不难发现,因为最多出现3次,那么假如有出现3次的数字,我们肯定是顺着排,比如说1 1 2 1 2 2 这个,我们排好一定是1 2 1 2 1 2,如果是三组或者更多出现三次的数字效果也是一样的。出现三次的数字在这样排布之后,一共有三部分,每部分的数字不同。这三部分形成了两个间隔,那么,不难想到,假如我们要求最小间隔最大,那么如果有二次出现的数字,一定是均匀分开,填充到这两个间隔中取。对于出现1次的数字,一次放到间隔I,一次放到间隔II,重复这个过程。也就是说,出现1次的数字,只有有偶数个的时候才会增加最小间隔。

如果没有出现3次的数字呢?对于多组出现2次的数字,我们的策略跟上面出现3次的是一样的。分成两部分,每部分数字不同。假如有出现1次的数字,就往这两部分形成的间隙中填充。这里与三次的间隙不同,1次数字不论奇偶,放进去多少个,间隔就增加多少。

分析完之后,我们解决问题的关键就变成了:如何统计出现3次、2次、1次数字的数量?

这里也是我当时一直卡住的点。我想用类似桶排的思想,数组的标号表示数字的值,但是,因为数字的范围是1e9所以必然会超。之后又想到,因为总个数是1e5,事前又不能确定n的大小,可以采用向量。
但是我读完放入向量之后,先进行sort排序,然后再用count来查询在整个vector中出现的次数。其实如果要用count,就没必要sort,sort之后完全可以回到下面AC代码中的判断方法。count,复杂度是O(n),加上前面的部分,所以TLE 。

今天补题的时候看到别人的方法,感觉自己真的蠢……统计出现的次数,sort之后相同元素都会在一起,我们记录不同元素下标之间的差,其实就是前一元素的数目,那只要O(n)扫一遍即可。

其实昨晚我最后的vector方法已经非常接近……也使用了记录上一不同值及其下标进行计算的方法,但是明明已经sort了还要用count查询,就很蠢……应该就是n次计算,每次都调用count导致了TLE。

最后还有一个坑。就是用数组统计个数的时候,究竟怎么计算的问题。我的lastmark记录的是最新不同元素的第一个的下标,一旦不同,进入这个分支我们cnt[__] 中 __ 统计的不是当前元素是3次2次还是1次,而是上一元素的,所以不需要+1(因为我们当前的坐标i本身就是新元素的第一个而不是上一元素的最后一个)。
还有就是对于最后一个位置,是很特殊的,因为我们到n-1就跳出,所以最后一次可能会来不及更新。最后一个位置有两种情况,一种是,最后一个元素它只出现了一次,那么就直接cnt[1]++即可;另一种情况是它出现了多次,但是这里要注意:我们此时i表示的是当前元素的最后一个的下标,而不同于上面对1~n-1的元素处理时表示新元素的第一个,所以这里要+1才是正确的!(因为这里WA了好久……想破头才发现是这里错了(。

#include <bits/stdc++.h>

using namespace std;

int n, ans;
int cntset[4];

int main()
{
    scanf("%d", &n);

    const int size_ = n;

    int numset[size_];
    memset(numset, 0, sizeof(numset));

    //0 - n-1读入n个数字
    for(int i = 0;i < n;i++)
        scanf("%d", &numset[i]);

    sort(numset, numset+size_);//排序

    int lastcon = -1, //上一次记录的成语
        lastmark = 0; //第一个不同数字出现的位置
    for(int i = 0;i < n;i++)
    {
        //printf("numset[%d] = %d\n", i, numset[i]);
        if(numset[i] != lastcon)
        {
            cntset[i-lastmark]++;//实际统计的不是当前的数字,而是上一个

            lastcon = numset[i];//当前数字是新一种数字的第一个
            lastmark = i;
        }
        if(i == n-1)//最后一个元素
        {
            if(lastmark == i)
                cntset[1]++;
            else//前面还有
                cntset[i-lastmark+1]++;
        }
    }


    if(cntset[3])//均只出现一次
        ans = cntset[3]-1+cntset[2]+cntset[1]/2;

    else if(cntset[2])//只有2 1
        ans = cntset[2]-1+cntset[1];

    else
        ans = n;

    printf("%d\n", ans);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值