P3052 [USACO12MAR]Cows in a Skyscraper G(状压

题意:给出n个物品,体积为w[i],现把其分成若干组,要求每组总体积<=W,问最小分组。(n<=18)P3052 [USACO12MAR]Cows in a Skyscraper G - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

分析:显然是状压,但是直接只枚举状态从 1 ~ (1 << n)肯定是不对的,为什么?因为存在后效性 —— 也就是奶牛的放入顺序是对结果有影响的,所以还要枚举当前集合最后放入的奶牛是谁,这样就可以间接实现【全排列】,状压DP对全排列的优化也在于此,有一种大麻绳小绳结的感觉, 所以评论区有很多人有疑问:为什么对于剩下的空间每次只要取最大的就行了,万一来个更小的把前面空的位置填了怎么办 ? 实际上他没理解这里已经实现了消除后效性的结果;

我跟题解代码刚好一个是刷表一个是填表,虽然是绿题,但还是想放一下:

我的:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6+15;
const int mo = 998244353;
#define pb push_back
#define pii pair<int,int>
#define ft first
#define sd second
#define ffor(i,a,b,c) for(int i=(a);i<(b);i+=(c))
#define For(i,a,b,c) for(int i=(a);i<=(b);i+=(c))
#define rfor(i,a,b,c) for(int i=(a);i>(b);i-=(c))
#define Rfor(i,a,b,c) for(int i=(a);i>=(b);i-=(c))
#define all(x) (x).begin(), (x).end()
#define debug1(x) cerr<<"! "<<x<<endl;
#define debug2(x,y) cerr<<"#  "<<x<<" "<<y<<endl;
int f[1 << 19], g[1 << 19];
void slv(){
	int n, w; cin >> n >> w;
	#define inf 1e9
	for (int i = 0; i < (1 << n); i++) {
		f[i] = inf;
	}
	// memset(f, 67, sizeof(f));
	vector<int> v(n);
	for (auto &x : v) cin >> x;
	f[0] = 1;
	g[0] = w;
	int mx = (1 << n) - 1;
	for (int i = 1; i <= mx; i++) {
		for (int j = 0; j < n; j++) {
			if (!(i & (1 << j))) continue;
			// cal(i);
			int nw = i ^ (1 << j);
			// cal(nw);
			if (g[nw] >= v[j]) {
				f[i] = min(f[i], f[nw]);
				g[i] = max(g[i], g[nw] - v[j]);
			} else {
				f[i] = min(f[i], f[nw] + 1);
				g[i] = max(g[i], w - v[j]);
			}
		}
	}
	cout << f[mx] << '\n';
}
int main(){
	ios::sync_with_stdio(false);cin.tie(0);
	slv();
}

题解的:

//洛谷P3052,提高+,省选- 
// 2018-09-20 22:05:49
// 状压dp 
#include<iostream>
#include<cstdio>
#include<cstring>
#include<noip2018 rp++>

using namespace std;

int n,w;
int a[20];
int f[1<<18];//f[i]状态为i的最小次数
int g[1<<18];//g[i]状态为i时,最后一个电梯的剩余体积

int main()
{
	scanf("%d%d",&n,&w);
	
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	
	memset(f,63,sizeof(f));
	f[0]=1;
	g[0]=w;
    //初始化
	
	for(int i=0;i<(1<<n);i++)
	//枚举状态 
	{
		for(int j=1;j<=n;j++)
		//枚举每一头奶牛 
		{
			if(i & (1<<(j-1))) continue;//如果这头奶牛已经在电梯里了
			if(g[i]>=a[j] && f[i | (1<<(j-1))]>=f[i])
			//如果这个电梯坐得下
			{
				f[i | (1<<(j-1))]=f[i];
				//让奶牛坐电梯
				//合并答案 
				g[i | (1<<(j-1))]=max(g[i | (1<<(j-1))],g[i]-a[j]);
				//更新剩余体积 
			}
			else if(g[i]<a[j] && f[i | (1<<(j-1))]>=f[i]+1)
			//如果这个电梯坐不下 
			{
				f[i | (1<<(j-1))]=f[i]+1;//电梯次数+1 
				g[i | (1<<(j-1))]=max(g[i | (1<<(j-1))],w-a[j]);
			}
		}
	}
	
	printf("%d",f[(1<<n)-1]);
	
	return 0;
}
//By Yfengzi

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值