CF1498B Box Fitting
壹、题目描述 ¶
给你 \(n\) 个长方形,每个长方形的长度保证为 \(2\) 的方幂,宽度均为 \(1\),问你如果一个宽度为 \(2\) 的盒子至少要多高才可以装下所有的长方形,不能把长方形立起来,也不能重叠。多测,共 \(t\) 组数据。
保证存在解,满足 \(t\le 5000,\sum n\le 10^5\).
贰、题解 ¶
推荐先看看提示,有助于你思考:
提示一:对于一组长条,我们有多种方案能够在最优策略下装下他们,但是我们总可以根据矩形大小将他们重新排列为一种相似的放置方案。这种放置方案是什么?
提示二:你能否证明,总是可以使用一个大的长方形替换一组连续的小长方形吗(这组连续小长方形的长度可能大于等于大的长方形长度)?
简要证明:
事实上,对于一组不增序列 \(\{a_0,a_1,a_2,...,a_n|a_i=2^x,x\in N\}\),如果 \(\sum_{i=1}^na_i\ge a_0\),则一定存在一个 \(k\) 使得 \(\sum_{i=1}^ka_i=a_0\),这个证明十分简单,设 \(a_0=2^k\),那么我们将 \(a_i(i\in[1,n])\) 一个一个加进 \(sum\) 时,不可能直接加上一个数就越过 \(2^k\) 那一位了,因为所有的 \(a_i(i\in[1,n])<a_0=2^k\) 的,所以,如果我们最后的 \(sum>a_0\),那么一定有一个 \(sum=a_0\) 的时刻。
所以,我们可以将所有的最优放置方案换成统一的一种:当长度较大的可以放在下面时,优先放长度较大的。
那么,就存在了一个贪心策略:优先放长度较大的长方形,再放长度较小的,这样可以让我们的答案达到最优。
答案具备单调性,我们同样可以考虑二分答案。但是,对于一个高度 \(h\),我们如何快速判断这个高度是否能装下所有矩形?
考虑处理一个数组 \(c_i\) 表示宽度为 \(2^i\) 的矩形个数。
从大到小枚举所有宽度(当然是枚举二的幂次),假设当前的宽度为 \(w_i\),显然,这个宽度对于高度 \(h\) 最多可以放下 \(f_i=h\times (W/w_i)\),如果 \(f_i<c_i\),显然这个高度不行。
然而,我们还需要更多的证明条件 —— 如果 \(2^{i+1}\) 宽度和 \(2^i\) 宽度的矩形打组合拳呢?也就是说,考虑 \(2^i\) 的矩形时,我们还需要考虑 \(2^j(j>i)\) 的矩形使用的空间,所以,我们的 \(c_i\) 不仅仅那么简单,真正的 \(c_i'=c_i+2\times c_{i+1}+4\times c_{i+2}+...\).
因此,我们只需要这样计算后缀和,再使用 \(f_i\) 和 \(c_i\) 进行比较即可。复杂度 \(\mathcal O(n+\log(w_{max})\log n)\).
贪心思路在非二幂次的情况下适用吗?显然是不行的,考虑一下这种数据:
6 13
6 6 4 4 3 3
然后,它 \(\rm GG\) 了。实际上,对于非二幂次的情况,证明都无法使用,这还能保证正确性?
叁、参考代码 ¶
贪心代码:
#define Endl putchar('\n')
#define mp(a, b) make_pair(a, b)
#define rep(i, l, r) for(int i=(l), i##_end_=(r); i<=i##_end_; ++i)
#define fep(i, l, r) for(int i=(l), i##_end_=(r); i>=i##_end_; --i)
#define fi first
#define se second
typedef long long ll;
template<class T>inline T fab(T x){ return x<0? -x: x; }
template<class T>T gcd(T x, T y){ return y? gcd(y, x%y): x; }
template<class T>inline T readin(T x){
x=0; int f=0; char c;
while((c=getchar())<'0' || '9'<c) if(c=='-') f=1;
for(x=(c^48); '0'<=(c=getchar()) && c<='9'; x=(x<<1)+(x<<3)+(c^48));
return f? -x: x;
}
const int maxn=1e5;
const int logn=19;
int t[maxn+5], n, W;
inline void input(){
n=readin(1), W=readin(1);
rep(i, 0, logn) t[i]=0;
int a;
rep(i, 1, n){
a=readin(1);
for(int j=0; j<=logn; ++j) if((a>>j)&1){
++t[j]; break;
}
}
}
signed main(){
int T=readin(1);
while(T--){
input();
int cnt=0, ans=0, cur;
while(cnt<n){
++ans, cur=0;
for(int p=logn; ~p; --p) while(t[p] && cur+(1<<p)<=W)
cur+=(1<<p), ++cnt, --t[p];
}
printf("%d\n", ans);
}
return 0;
}