*题解: CF2229F Load Unbalancing
解析
看到数据范围先想到状压 dp,设 \(f_{S}\) 为已添加元素集合为 \(S\) 时,\(b\) 中元素最大值。然而,这个状态并不具有最优子结构,\(S\) 的最优情况不能从 \(S\) 的子集的最有情况转移得到,所以还是回去找性质吧。
考虑 \(\max(b)\) 的组成,猜想其必定可以在最后加入 \(\max(a)\) 得到。也就是说在加入最后一个元素前, \(\min(b) + \max(a) \ge \max(b)\)。
假设在加入最后一个元素前 \(\min(b) + \max(a) < \max(b)\),考虑此时 \(\max(b)\) 的构成。设 \(\max(b)\) 去掉最后添加的元素 \(x\) 后变为 \(y\),那么由于每次操作会将元素加到 \(b\) 的最小值,所以应当有 \(y \le \min(b)\)。又因为 \(x \le \max(a)\),故 \(x + y = \max(b) \le \min(b) + \max(a)\),矛盾。
这样,去掉一个 \(\max(a)\),问题转化为求 \(\min(b)\) 的最大值。考虑二分答案 \(x\),现在要判断能否构造出 \(\min(b) \ge x\) 的方案。有一个必要条件是 \(a\) 能至少分出 \(k\) 组数,每组里面的数的和大于等于 \(x\)。事实上这也是充分的,将分出来的 \(k\) 组数一一对应 \(b\) 中的 \(k\) 个位置,要在哪个位置上加就在对应那组中取出一个数加上。
判断能否分组可以使用状压 dp。设 \(f_S\) 表示已经分出来了的元素集合为 \(S\),最多分出多少组;\(g_S\) 表示 \(f_S\) 最大的情况下,新的元素和小于 \(x\) 的一组中元素最大和。转移比较常规。
时间复杂度 \(O(2^n \cdot n \log V)\)。
代码
注意开 long long。
/*
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int N = 18,M = 10 + 5,L = 19,L2 = 17,mod = 998244353;
int a[N + 5];
ll f[(1 << N) + 3][2];
int n,k;
bool chk(ll x){
int m = n - 1;
for(int i=0;i < (1 << m);i++){
f[i][0] = f[i][1] = 0;
}
for(int i=0;i < (1 << m);i++){
for(int j=0;j<m;j++){
if(!(i & (1 << j))){
int ni = i | (1 << j);
ll f0 = f[i][0],f1 = f[i][1] + a[j + 1];
if(f1 >= x){
f0++;
f1 = 0;
}
if(f[ni][0] < f0){
f[ni][0] = f0;
f[ni][1] = f1;
}else if(f[ni][0] == f0 && f[ni][1] < f1){
f[ni][1] = f1;
}
}
}
}
return f[(1 << m) - 1][0] >= k;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
int T;
cin>>T;
while(T--){
cin>>n>>k;
ll sum = 0;
for(int i=1;i<=n;i++){
cin>>a[i];
sum += a[i];
}
sort(a + 1,a + n + 1);
ll l = 0,r = sum - a[n];
while(l < r){
ll mid = (l + r + 1) >> 1;
if(chk(mid)){
l = mid;
}else{
r = mid - 1;
}
}
cout<<r + a[n]<<'\n';
}
return 0;
}

浙公网安备 33010602011771号