这道题可以用一种十分巧妙的状压做,并且很好写。
注意到牛数小于18,我们用一个二进制串表示状态,一位为1表示这只牛选过了,0表示没选过。
用 和
两个数组,每个状态分别表示 到达该状态需要的最少组数 以及 该状态最优分组中和最小一组的和。我们据此放入新的牛,此时显然更易放入和最小的组,也更容易减少分组数量。
可以发现,这种类似贪心的dp方法却会避免贪心时的错误,因为dp当前状态会充分地考虑到通过其每一个子集的最优子结构转移:
6 8
2 3 4 4 5 6
面对这组数据,贪心会分成 2 3|4 4|5|6 ,答案却应是 2 6|3 5|4 4,因为贪心只能从前往后找,而dp中每一种状态时每一只新加入的牛都会来尝试更新它,从 {2,6} 开始的转移便为我们提供了正确的方向。
#include<bits/stdc++.h>
#define N 300009
#define M 500009
#define INF 0x3f3f3f3f
#define mod 998244353
using namespace std;
typedef long long ll;
typedef long double ldb;
typedef pair<int,int> pii;
int n,m,t,w;
int f[N],g[N],v[29];
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>w;
for(int i=0;i<n;i++)cin>>v[i];
memset(f,INF,sizeof(f));
memset(g,INF,sizeof(g));
f[0]=1,g[0]=0;
for(int i=0;i<(1<<n);i++){
for(int j=0;j<n;j++){
if(!(i&(1<<j))){
if(g[i]+v[j]<=w&&f[i|(1<<j)]>=f[i]){
f[i|(1<<j)]=f[i];
g[i|(1<<j)]=min(g[i|(1<<j)],g[i]+v[j]);
}
if(g[i]+v[j]>w&&f[i|(1<<j)]>=f[i]+1){
f[i|(1<<j)]=f[i]+1;
g[i|(1<<j)]=min(g[i|(1<<j)],v[j]);
}
}
}
}
cout<<f[(1<<n)-1]<<endl;
return 0;
}