P5911 [POI2004] PRZ(状压dp,枚举子集的子集)

https://www.luogu.com.cn/problem/P5911

[POI2004] PRZ

题目背景

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

题目描述

桥已经很旧了, 所以它不能承受太重的东西。任何时候队伍在桥上的人都不能超过一定的限制。 所以这只队伍过桥时只能分批过,当一组全部过去时,下一组才能接着过。队伍里每个人过桥都需要特定的时间,当一批队员过桥时时间应该算走得最慢的那一个,每个人也有特定的重量,我们想知道如何分批过桥能使总时间最少。

输入格式

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

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

输出格式

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

样例 #1

样例输入 #1

100 3
24 60
10 40
18 50

样例输出 #1

42

提示

对于 \(100\%\) 的数据,\(100\le W \le400\)\(1\le n\le 16\)\(1\le t\le50\)\(10\le w\le100\)

解释
我们用 S 表示所有人构成集合的一个子集,设 t(S) 表示 S 中人的最长过桥时间,w(S) 表示 S 中所有人的总重量,f(S) 表示 S 中所有人全部过桥的最短时间,则:

\[\begin{cases} f(\varnothing)=0,\\ f(S)=\min\limits_{T\subseteq S;~w(T)\leq W}\left\{t(T)+f(S\setminus T)\right\}. \end{cases} \]

需要注意的是这里不能直接枚举集合再判断是否为子集,而应使用 子集枚举,从而使时间复杂度为 O(3^n).、

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn=1<<17;
int a, b, w[17], t[17];
int T[maxn],W[maxn],f[maxn];
int main(){
	cin>>a>>b;
	for(int i=0;i<b;i++){
		cin>>t[i]>>w[i];
	}
	for(int i=0;i<(1<<b);i++){
		for(int j=0;j<b;j++){
			if((i>>j)&1){
				W[i]+=w[j];
				T[i]=max(T[i],t[j]);
			}
		}
	}
	memset(f,0x3f3f3f3f,sizeof(f));
	f[0]=0;
	for(int i=0;i<(1<<b);i++){
		for(int j=i;;j=i&(j-1)){
			if(W[i^j]<=a){
				f[i]=min(f[i],f[j]+T[i^j]);
			}
			if(!j) break;
		}
	}
	printf("%d\n", f[(1 << b) - 1]);
	return 0;
} 

状压 dp。把每一个人选不选压为二进制。

如 8 个人中选了 1,3 和 4
就可以表示为 00001101。C[i] 表示当 i 这些人为一队时的总重量。T[i] 表示当 i 这些人为一队时里面最慢的那个人要的时间。

状态转移方程:
if(C[i ^ j] <= w) f[i] = min(f[i], f[j] + T[i ^ j]);

含义是将 i 这些人拆为 i⊕j 以及 j 两组,当 i⊕j 这一组的总重量不超过桥的承受能力时就可以用更新 f[i]。

写成下面这样也可以:
if (C[j] <= w) f[i] = min(f[i], f[i ^ j] + T[j]);

posted @ 2024-09-13 21:15  lipu123  阅读(15)  评论(0)    收藏  举报