【题解】CF2229F
0
1:转化
可以发现,将最大值最后放一定最优,此时问题转化为分配其余 \(n-1\) 个点,使得他们最小值最大
2:二分
不过这样还是很难刻画状态,注意到最小值最大,提示二分
我们二分最小值,转为判定问题
3:O(3^n)
\(dp[S]:\) 集合 \(S\) 最多可以填满多少个桶
集合枚举,发现 \(O(3^n)TLE\)
4:O(2^n)
对于一个状态,发现其填桶构造分为两部分,一部分是填满了多少个桶,另外一个是未填满的一个桶,现在 \(a\) 之和是多少
刻画状态,表示二元值
-
\(dp[S].x:\) 用状态 \(S\) ,填满组数最大值
-
\(dp[S].y:\) 用状态 \(S\) ,零碎桶 \(a\) 之和
5:构造
我们上面的思路尚未考虑题目要求的构造方式,不过问题不大,显然可以构造出来
因为每个桶,减去桶中任意一个 \(a\) ,都会 \(<=mid\) ,所以可以构造
6:代码
// Problem: F. Load Unbalancing
// Contest: Codeforces - Spectral::Cup 2026 Round 2 (Codeforces Round 1100, Div. 1 + Div. 2)
// URL: https://codeforces.com/contest/2229/problem/F
// Memory Limit: 256 MB
// Time Limit: 1500 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include<bits/stdc++.h>
#define int long long
#define Pair pair<int,int>
#define x first
#define y second
using namespace std;
const int N=20;
int a[N];
Pair dp[1<<N];
int n,m;
bool check(int mid){
//dp[S].x:用状态S,填满组数最大值
//dp[S].y:用状态S,零碎之和
for(int S=0;S<(1<<n);S++) dp[S]={0,0};
for(int S=0;S<(1<<n);S++){
for(int j=0;j<n;j++){
if(S>>j&1) continue;
int T=S^(1<<j);
Pair cur=dp[S];
cur.y+=a[j];
if(cur.y>=mid) cur={cur.x+1,0};
dp[T]=max(dp[T],cur);
}
}
int ans=0;
for(int S=0;S<(1<<n);S++) ans=max(ans,dp[S].x);
return ans>=m;
}
void solve(){
cin>>n>>m;
for(int i=0;i<n;i++) cin>>a[i];
sort(a,a+n);
int add=a[n-1];n--;
int l=0,r=add*n;
while(l<r){
int mid=l+r+1>>1;
if(check(mid)) l=mid;
else r=mid-1;
}
cout<<l+add<<"\n";
}
signed main(){
int t;cin>>t;
while(t--) solve();
return 0;
}
7:历程
注意到数据范围,我想到状压,然后尝试刻画状态,发现根本刻画不出来
其实这道题只要首先注意到扔掉最大 \(a\),然后化成一个二分+状压问题
然后再注意到性质,从状态定义角度优化dp就可以做出来了
8:反思与评价
8.1
看到数据范围,可以想到状压,但是如果刻画不出来,可以搭配其他算法转化问题
一道状压题不一定是纯状压
8.2
一个TLE的状压,可以观察其性质,重新定义dp进行优化
8.3
一定要先分析性质,再dp设计

浙公网安备 33010602011771号