大意:给定$nk$块木板, 要制作$n$个$k$块板的桶, 要求任意两桶容积差不超过$l$, 每个桶的容积为最短木板长, 输出$n$个桶的最大容积和
假设最短板长$m$, 显然最后桶的体积都在$[m,m+l]$范围内, 就是说需保证每个桶都至少有一块板在$[m,m+l]$范围.
考虑贪心逐步调整, 假设$m+l$足够多的话, 可以先尽量连续得选最短的边做桶, 最后将$m+l$分给剩余桶即为最大容积.
如果不够多的话, 就要选出$m+l-1$分给剩余桶, 还不够就选择$m+l-2$, 直到所有桶都分到为止.
离散化后预处理一下前缀和可以达到复杂度$O(nlogk+nlogn)$
#include <iostream> #include <algorithm> #include <cstdio> #include <vector> #define REP(i,a,n) for(int i=a;i<=n;++i) using namespace std; typedef long long ll; const int N = 4e5+10, INF = 0x3f3f3f3f; int n, k, l; int a[N], b[N], cnt[N], s[N]; //用a记录离散化后的值, b是离散化辅助数组 //cnt记录每个离散化后的值的出现次数, s统计cnt的前缀和 ll solve(int n, int x) { //当前最大的为x, 还要制作n个桶的最大容积和 int t = (s[x-1]+k-1)/k; if (t+cnt[x]>=n) { //x数量足够, 直接贪心分配 ll ans = 0; REP(i,1,n) { if (a[(i-1)*k+1]<x) ans += b[a[(i-1)*k+1]]; else ans += b[x]; } return ans; } //x不够的话, 先尽量把x分完, 求出第一个比x小的数, 递归计算 auto p = lower_bound(a+1,a+1+n*k,x); return solve(n-cnt[x],*(--p))+(ll)cnt[x]*b[x]; } int main() { scanf("%d%d%d", &n, &k, &l); REP(i,1,n*k) scanf("%d", a+i); sort(a+1,a+1+n*k); ll mx = a[1]+l; if (a[n]>mx) return puts("0"),0; REP(i,1,n*k) b[i]=a[i]; *b = unique(b+1,b+1+n*k)-b-1; REP(i,1,n*k) a[i]=lower_bound(b+1,b+1+*b,a[i])-b; REP(i,1,n*k) ++cnt[a[i]]; REP(i,1,*b) s[i]=s[i-1]+cnt[i]; int p = upper_bound(b+1,b+1+*b,mx)-b-1; printf("%lld\n", solve(n,p)); }
看了下别人题解, 发现没必要这么麻烦, 上述分析已经知道, 最优结构一定是先连续选一段最小的, 再将$[m,m+l]$中剩余的逐个分给剩余桶
假设连续部分做了x个桶, 剩余部分y个桶, 就有$xk+y<=s[m+l],x+y=n$
解出x的最大值, 就可以直接得到最优解的结构了