题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5884
题意:有n个有序序列,每个序列有ai个元素,现在有一个程序每次可以归并最多k个序列,最终把所有的序列合并成一个,每次归并所需要的代价是所有序列的长度和;
现有一个代价界限T,就是总的代价不能超过T,求符合条件的最小的K;
当给定一个K的准确值时,我们可以每次选择最小的k个数进行合并;所以我们可以用优先队列来处理,但是由于范围比较大,可以优化一下,只让合并形成的序列进入优先队列,每次取数组和队列中较小的一个即可;
对于k可以用二分的方法来求,但是会发现当n=5,k=4的时候,当直接运用上面的做法,最后没有k个数,那么就不能保证单调性了,为了保证每次都是k个序列进行合并,我们可以运用补0的方法进行处理,这样就保证了二分的正确性;
#include<stdio.h> #include<string.h> #include<algorithm> #include<iostream> #include<vector> #include<queue> #include<set> using namespace std; #define met(a, b) memset(a, b, sizeof(a)) #define N 100005 #define INF 0x3f3f3f3f typedef long long LL; int M, n, a[N], sum[N]; bool Judge(int k) { int r = (n-k)%(k-1), Index; LL s; if(r == 0)///如果已经满足每次都是k个数了,直接取数组的前k项即可; { s = sum[k]; Index = k+1; } else///否则就先把前r+1个数进行合并; { s = sum[r+1]; Index = r+2; } priority_queue<LL>Q; Q.push(s); while(!Q.empty() || Index <= n) { if(Index > n && Q.size() == 1) break;///当只剩下队列中的一个数时,说明已经合并完成了; int cnt = 0; LL part = 0; while(cnt<k && (!Q.empty() || Index<=n)) { if(Index <=n && (Q.empty() || a[Index] <= Q.top())) part += a[Index++]; else { part += Q.top(); Q.pop(); } cnt++; } s += part; if(s > M) break; Q.push(part); } return s<=M; } int main() { int T; scanf("%d", &T); while(T--) { met(a, 0); met(sum, 0); scanf("%d %d", &n, &M); for(int i=1; i<=n; i++) scanf("%d", &a[i]); sort(a, a+n+1); for(int i=1; i<=n; i++) sum[i] = sum[i-1] + a[i]; int L = 2, R = n, ans = 2; while(L <= R) { int Mid = (L+R)/2; if(Judge(Mid)) { R = Mid - 1; ans = Mid; } else L = Mid + 1; } printf("%d ", ans); } return 0; } /* 5 15 0 0 0 0 0 */