P3052 [USACO12MAR] Cows in a Skyscraper G & P10483 小猫爬山
这俩题互为双倍经验,所以我们只讲其中一道。以 P3052 为例。
状压 dp 好题。我第一次做这个题的时候还以为要枚举子集的来着,然后就T了。后来想想其实没这么麻烦的。 (但凡是枚举子集也不能是 18)
原题稍微转换一下,就是让我们开最小数量的电梯,使每个电梯都不超重。
我们设 \(dp_{i,S}\) 表示当前开了 \(i\) 个电梯,当前已经上电梯的奶牛集合是 \(S\),最后一班电梯(也就是电梯 \(i\))的总重量最小是 \(dp_{i,S}\)。
(这里我考虑了半天为什么是最后一班电梯……QAQ,待会会讲这个问题。)
刷表法转移这个似乎更快一点。我们就考虑当前这个状态能转移到哪去。
我们枚举当前所有没进电梯的奶牛 \(k\),如果下一个进入电梯的是它,那么只有两种情况:一种是新开一个电梯让它进去,一种是让它进最后一班电梯。
为什么不考虑前面的电梯呢?因为我们发现,它坐的如果是靠前一点的电梯的话,那当这班电梯还是最后一班电梯的时候应该考虑过这个情况了。
我们细想一下,它在这班电梯时可能有多个奶牛和它一班,它和其他奶牛的所有可能组合,都应该在这个靠前一点的电梯还是最后一班电梯时考虑过了。
所以我们证明了,只考虑最后一班电梯和新开一班电梯的做法没有问题。
那代码就很好懂了,自行观看即可:(P10483同理)
P3052&P10483
#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
int x=0,f=1;char c=getchar();
while(c<48){
if(c=='-') f=-1;
c=getchar();
}
while(c>47) x=(x<<1)+(x<<3)+(c^48),c=getchar();
return x*f;
}
const int N=20;
const int inf=1e16;
int n,W,dp[N][(1<<N)],a[N];
signed main(){
n=read(),W=read();
for(int i=0;i<n;i++){
a[i]=read();
}
for(int S=1;S<(1<<n);S++){
for(int i=1;i<=n;i++){
dp[i][S]=inf;
}
}
//初始化,第一个电梯里面只有一头猪的情况
for(int i=0;i<n;i++){
dp[1][(1<<i)]=a[i];
}
for(int S=0;S<(1<<n);S++){
for(int i=1;i<=n;i++){
//警示后人:i可以取到n,此时是在最后一个电梯里面继续加猪的状态
//警示后人:如果当前状态不合法不能进行转移,否则43行有问题
if(dp[i][S]==inf) continue;
for(int k=0;k<n;k++){
if(S&(1<<k)) continue;
//进最后一个电梯的情况
if(dp[i][S]+a[k]<=W) dp[i][(S|(1<<k))]=min(dp[i][(S|(1<<k))],dp[i][S]+a[k]);
//新开一个电梯的情况
dp[i+1][(S|(1<<k))]=min(dp[i+1][(S|(1<<k))],a[k]);
}
}
}
//如果开了i个电梯,就能装下所有猪的话,说明最小就是i个电梯
for(int i=1;i<=n;i++){
if(dp[i][((1<<n)-1)]<inf){
printf("%lld",i);
exit(0);
}
}
return 0;
}

浙公网安备 33010602011771号