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;
}
posted @ 2023-06-07 10:56  霜木_Atomic  阅读(42)  评论(0编辑  收藏  举报