1. 程式人生 > 實用技巧 >P5911 [POI2004]PRZ (狀態壓縮dp+列舉子集)

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)

程式碼

對於這道題,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<iostream>
#include<cstdio>
#include<algorithm>
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<<j)) == 0)//判斷這個人在i這個狀態是否已經乘船,沒乘船的話,可以轉移得到下一個狀態
			{
				tim[i | (1<<j)] = max(tim[i],t[j]);//i|(1<<j)即把i的第j位賦1,就像於第j個人坐了船後,i所變成的狀態
				maxw[i |(1<<j)] = maxw[i] + w[j]; //進行轉移
			}
		}
	}
	for(int i = 0; i < base[n]; i++) f[i] = 2333333;//初始化為無窮大
	f[0] = 0;
	for(int i = 1; i < base[n]; i++)//列舉每個狀態
	{
		for(int j = i; j; j = (j-1) & i)//列舉子集
		{
			if(maxw[j] <= m) f[i] = min(f[i],f[i-j] + tim[j]);//j你可以理解為這次要運的狀態,i-j就是i沒運j之前i的狀態
		}
	}
	printf("%d\n",f[base[n]-1]);
	return 0;
}