好题
题意:给你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; }