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 中所有人全部过桥的最短时间,则:
需要注意的是这里不能直接枚举集合再判断是否为子集,而应使用 子集枚举,从而使时间复杂度为 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]);