P5911 [POI2004]PRZ (状态压缩dp+枚举子集)

题目背景

一只队伍在爬山时碰到了雪崩,他们在逃跑时遇到了一座桥,他们要尽快的过桥。

题目描述

桥已经很旧了, 所以它不能承受太重的东西。任何时候队伍在桥上的人都不能超过一定的限制。 所以这只队伍过桥时只能分批过,当一组全部过去时,下一组才能接着过。队伍里每个人过桥都需要特定的时间,

当一批队员过桥时时间应该算走得最慢的那一个,每个人也有特定的重量,我们想知道如何分批过桥能使总时间最少。

输入格式

第一行两个数: W 表示桥能承受的最大重量和 n 表示队员总数。

接下来 n 行:每行两个数: t 表示该队员过桥所需时间和 w 表示该队员的重量。

输出格式

输出一个数表示最少的过桥时间。

输入输出样例

输入 #1

100 3
24 60
10 40
18 50

输出 #1

42

说明/提示

对于 100% 的数据,100≤W≤400,1≤n≤16,1≤t≤50,10≤w≤100。

前置芝士

  1. 位运算

  2. 枚举子集

首先,我们先看一下枚举子集是什么东西。

在状态压缩dp时,我们一般的套路就是枚举两个状态\(i\)\(j\),判断 \(j\) 是否是 \(i\) 的子集,这样来说复杂度时O(4^n)

但,根据二项式定理,一个集合的子集最多有3^n 严格枚举的话,可以将复杂度变为O(3^n)

代码

P5911 [POI2004]PRZ (状态压缩dp+枚举子集)_第1张图片

对于这道题,n的范围很小,我们可以考虑对n进行状态压缩

f[i] 表示达到 \(i\) 这个状态所需要的最小时间 \(i\)时一个n位的二进制数。

转移的话,我们可以枚举\(i\)的子集,就是考虑这次有哪些人乘船

f[i] = min(f[i],f[i-j] + tim[j]);//i是我们想达到的状态,j是这次要运的状态,i-j是没运j这次之前的状态

对于,每个状态所花费的时间,我们可以在之前就预处理出来。

当然,你也可以在枚举j的时候算,只是这样你多算了很多状态,你就会稳稳的TLE

看不懂的童鞋,下面代码有注释。

代码

#include
#include
#include
using namespace std;
int n,m,t[20],w[20],base[20],f[65540],tim[65540],maxw[65540];
inline int read()
{
	int s = 0, w = 1; char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9'){s = s * 10+ch -'0'; ch = getchar();}
	return s * w; 
}
int main()
{
	m = read(); n = read();
	for(int i = 0; i <= n-1; i++)
	{
		t[i] = read(); w[i] = read();
	}
	base[0] = 1;
	for(int i = 1; i <= n; i++) base[i] = base[i-1] * 2;//处理一下2的进制
	for(int i = 0; i < base[n]; i++)//枚举每个状态
	{
		for(int j = 0; j < n; j++)//枚举每个人
		{
			if((i & (1<

你可能感兴趣的:(P5911 [POI2004]PRZ (状态压缩dp+枚举子集))