• HDU 5884 Sort(2016年青岛网络赛 G 二分+贪心+小优化)


    好题

    题意:给你n<=100000个数,每个数范围[0,1000],然后给你一个最大的代价T,每次最多合并k个数成为一个数,代价为k个数的总和。问最后合成1个数的总代价不大于T的最小k

    题解:我们首先知道当k越大,总代价会越小,这样我们就找到了单调性,可以二分k看是否满足代价(又见最大值最小化问题)。然后我们贪心寻找固定k的最小代价,可以想到每次取前k个最小的值合并成一个,再放入数组中继续这个操作,直到最后变成一个数,这样我们可以直接使用优先队列模拟。

    但是直接做会超时,所以我就YY了一个优化的方法。我们可以看到数字个数虽然很多,但是范围很小(数字很密集),所以我们可以首先使用数组记录每种值的个数,然后合并时使用一个指针无回溯向后寻找。这样很方便,因为每次k个数的和一定不小于最大的那个数(指针的位置),这时我们就可以把这个和放入后面的数组中,保证了指针无回溯。但是数字大了后会很低效,所以我们在这时再使用优先队列就好。

    可我就这样一直wa,wa,wa。究其原因就是贪心错了。

    给个数据:

    5 18

    1 2 3 4 5

    答案是4,但是直接这样计算答案就不是4。。。

    因此还有一个问题就是:每次合并k个数意味着减少(k-1)个数,最后变成1个数意味着总共减少(n-1)个数,而(n-1)%(k-1)不等于0时我们需要首先合并前(n-1)%(k-1)+1个最小的数,接着再合并k个数,这样就不会出现最后合并时少于k个数的情况了。

    #include<set>
    #include<map>
    #include<queue>
    #include<stack>
    #include<cmath>
    #include<vector>
    #include<string>
    #include<cstdio>
    #include<cstring>
    #include<stdlib.h>
    #include<iostream>
    #include<algorithm>
    using namespace std;
    #define eps 1E-8
    /*注意可能会有输出-0.000*/
    #define Sgn(x) (x<-eps? -1 :x<eps? 0:1)//x为两个浮点数差的比较,注意返回整型
    #define Cvs(x) (x > 0.0 ? x+eps : x-eps)//浮点数转化
    #define zero(x) (((x)>0?(x):-(x))<eps)//判断是否等于0
    #define mul(a,b) (a<<b)
    #define dir(a,b) (a>>b)
    typedef long long ll;
    typedef unsigned long long ull;
    const int Inf=1<<28;
    const double Pi=acos(-1.0);
    const int Mod=1e9+7;
    const int Max=100010;
    int pos[Max],num[Max];
    struct node
    {
        int val;
        bool operator<(const node &c)const
        {
            return val>c.val;
        }
    };
    priority_queue<node> pque;//小顶堆
    ll Check(int sma,int mid,int n)//从sma开始到n每次最多合并mid个最小代价
    {
        ll sum=0ll;
        node tem;
        while(!pque.empty())
            pque.pop();
        memset(pos,0,sizeof(pos));
        for(int i=sma; i<n; ++i)//初始化
        {
            if(num[i]<(ll)Max)
                pos[(int)num[i]]++;
            else
            {
                tem.val=num[i];
                pque.push(tem);
            }
        }
        int j=0,tmp,tmp2;
        n=n-sma;//个数可能变小
        while(n>1)
        {
            tmp=mid;
            tmp2=0;
            while(j<Max&&n>0&&tmp>0)//数组里有
            {
                if(pos[j]==0)//只能在这儿才++
                {
                    ++j;
                    continue;
                }
                tmp2+=j;
                pos[j]--;
                n--;
                tmp--;
            }
            while(!pque.empty()&&tmp>0&&n>0)
            {
                tmp2+=pque.top().val;
                pque.pop();
                tmp--;
                n--;
            }
            sum+=tmp2;
            if(tmp2>=Max)//数组里存不下
            {
                tem.val=tmp2;
                pque.push(tem);
            }
            else//可以存在数组里(并且一定存在最后访问的位置及后面)
                pos[tmp2]++;
            n++;
        }
        return sum;
    }
    int Dic(int n,int sma,int big,int m)//最大值最小化问题
    {
        sort(num,num+n);
        int tmp,tmp2,tmp3,mm=m;
        while(sma<big)
        {
            int mid=((sma+big)>>1);
            tmp=0;
            m=mm;
            if((n-1)%(mid-1))//**每次合并mid个后有余数,余下的首先合并更优**
        {
            tmp=(n-1)%(mid-1)+1;
            tmp2=0;
            for(int i=0;i<tmp;++i)
            {
                tmp2+=num[i];
                m-=num[i];
            }
            tmp--;
            tmp3=num[tmp];
            num[tmp]=tmp2;
        }
            if(Check(tmp,mid,n)>m)//满足单调性
                sma=mid+1;
            else
                big=mid;
                if(tmp)//注意num数组要修改回来
            num[tmp]=tmp3;
        }
        return big;
    }
    int main()
    {
        int t,n;
        ll m;
        scanf("%d",&t);
        while(t--)
        {
            scanf("%d %I64d",&n,&m);
            for(int i=0; i<n; ++i)
                scanf("%I64d",&num[i]);
                if(n<=1)
                    printf("0
    ");
                    else if(n==2)
                        printf("2
    ");
                else
            printf("%d
    ",Dic(n,2,n,m));
        }
        return 0;
    }
  • 相关阅读:
    [Swift]LeetCode20. 有效的括号 | Valid Parentheses
    [Swift]LeetCode14. 最长公共前缀 | Longest Common Prefix
    [Swift]LeetCode13. 罗马数字转整数 | Roman to Integer
    [Swift]LeetCode9. 回文数 | Palindrome Number
    [Swift]LeetCode7. 反转整数 | Reverse Integer
    [Swift]LeetCode2. 两数相加 | Add Two Numbers
    [Swift]LeetCode1 .两数之和 | Two Sum
    3人小公司1年的创业成本
    3人小公司1年的创业成本
    OpenJDK源码研究笔记(二)-Comparable和Comparator2个接口的作用和区别(一道经典的Java笔试面试题)
  • 原文地址:https://www.cnblogs.com/zhuanzhuruyi/p/5879786.html
Copyright © 2020-2023  润新知