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;
}
原文地址:https://www.cnblogs.com/zhuanzhuruyi/p/5879786.html