P3052 [USACO12MAR] Cows in a Skyscraper G & P10483 小猫爬山

题目传送门 题目传送门2

博客传送门

这俩题互为双倍经验,所以我们只讲其中一道。以 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;
}
posted @ 2025-11-11 16:39  qwqSW  阅读(5)  评论(0)    收藏  举报