P5911 [POI2004]PRZ 题解
看到 \(n\) 很小,想到状压dp。
首先我们可以预处理出来每个状态的过桥时间和总重量。对于一个状态 \(s\),枚举它的子集 \(p\) ,令 \(q\) 为 \(p\) 在 \(s\) 中的补集,有 $$ dp_s = min(dp_p + dp_q)$$。
如果发现集合 \(s\) 的总重量小于桥的限制,将它初始化为该状态的过桥时间即可;否则,赋值正无穷。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 70000;
inline int read(){
int x = 0; char ch = getchar();
while(ch<'0' || ch>'9'){
ch = getchar();
}
while(ch>='0'&&ch<='9'){
x = x*10+ch-48;
ch = getchar();
}
return x;
}
int mxw, n;
int t[20], w[20];
int dp[N];
int tim[N], sum[N];
int main(){
mxw = read(), n = read();
for(int i = 0; i<n; i++){
t[i] = read(), w[i] = read();
}
for(int i = 1; i<=(1<<n)-1; i++){
for(int j = 0; j<n; j++){
if((i>>j)&1){
tim[i] = max(t[j], tim[i]);
sum[i]+= w[j];//预处理每个集合对应的重量和过桥时间。
}
}
}
memset(dp, 0x3f, sizeof(dp));
dp[0] = 0;
for(int i = 1; i<=(1<<n)-1; i++){
if(sum[i]<=mxw) {
dp[i] = tim[i];//如果能过,则最优时间一定为这个集合所有人过桥的时间。
} else {
for(int s = i; s; s = (s-1)&i) {
int ss = i^s;
dp[i] = min(dp[s]+dp[ss], dp[i]);//拆成若干个集合的和。
}
}
}
printf("%d\n", dp[(1<<n)-1]);
return 0;
}