题目传送门


前言

虽然是一道比较好想的状压 d p dp dp,但是它深深使我我感受到【简洁而快速的二进制操作】对状压 d p dp dp 的重要意义。


暴力思路 & 代码

d p s dp_s dps 为表示:状态 s s s 所代表的人们过桥所需的最少时间( s s s 的二进制中,从右往左数第 i i i 若为 1 1 1,那么就代表这个人在过桥队伍当中;若为 0 0 0,就代表不在这一波过桥)。
最后的答案是 d p ( 1 < < n ) − 1 dp_{(1 << n) - 1} dp(1<<n)1(即二进制上的每一位都是 1 1 1)。

我们先预处理每种状态的总重过桥时间
然后从小到大枚举状态 i i i,再枚举状态 i i i子状态 j j j(子状态就是指:【 s s s 所代表过桥的人组成的集合】的子集),然后看子状态 j j j 的总重是否能过桥,能的话就转移,不能的话就算了。
具体见代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const int maxn = 16 + 7;
const int maxs = (1 << 16) + 7;
const int inf  = 0x3f3f3f3f;

int n, m, ns;
int w[maxn], t[maxn];
int sw[maxs], mt[maxs];

int dp[maxs];
int main() {
	scanf("%d%d", &m, &n);
	for (int i = 1; i <= n; ++i)
		scanf("%d%d", t + i, w + i);
	ns = (1 << n) - 1;
	for (int s = 0; s <= ns; ++s)
		for (int i = 0; i < n; ++i)
			if (s & (1 << i))
				sw[s] += w[i + 1],
				mt[s] = max(mt[s], t[i + 1]);
				
	memset(dp, inf, sizeof(dp));
	dp[0] = 0;
	for (int i = 0; i <= ns; ++i) {
		for (int j = 0; j <= ns; ++j) {
			if (i & j != j) continue;  // 这一步就是用来判断 j 是否是 i 的子状态的
			if (sw[j] <= m) dp[i] = min(dp[i], mt[j] + dp[i ^ j]);
		}
	}
	printf("%d\n", dp[ns]);
	return 0;
}

时间复杂度是 O ( 2 n × 2 n ) O(2 ^ n \times 2 ^ n) O(2n×2n) 跑满的,交上去只能得 85   p t s 85 \ pts 85 pts


优化

说是优化,其实就是舍去一些不必要的状态(即: j j j 不是 i i i 子状态的情况),并不能优化时间复杂度,但是确实可以通过此题,因为时间复杂度远远跑不满。

先看看改了哪里:

for (int j = i; j; j = i & (j - 1))

其实就是改了枚举 i i i 的子状态的方法,即:不再枚举所有状态然后判断哪个是子状态,而是直接枚举子状态

这个的妙处在于,它每次都会把 j j j 目前的最后一个 1 1 1 变成 0 0 0,然后再把这位之前的变成与 i i i 对应位置相同的状态。
比如:
i = 0110   1100 ,   j 0 = i = 0110   1100 i = 0110 \ 1100, \ j_0 = i = 0110\ 1100 i=0110 1100, j0=i=0110 1100
下边开始进行模拟运算:
j 1 = i   &   ( j 0 − 1 ) = 0110   1000 j 2 = i   &   ( j 1 − 1 ) = 0110   0100 j 3 = i   &   ( j 2 − 1 ) = 0110   0000 j 4 = i   &   ( j 3 − 1 ) = 0100   1100 j 5 = i   &   ( j 4 − 1 ) = 0100   1000 j 6 = i   &   ( j 5 − 1 ) = 0100   0100 j 7 = i   &   ( j 6 − 1 ) = 0100   0000 j 8 = i   &   ( j 7 − 1 ) = 0010   1100 j 9 = i   &   ( j 8 − 1 ) = 0010   1000 j 10 = i   &   ( j 9 − 1 ) = 0010   0100 j 11 = i   &   ( j 10 − 1 ) = 0010   0000 . . . j_1 = i \ \& \ (j_0 - 1) = 0110 \ 1000 \\ j_2 = i \ \& \ (j_1 - 1) = 0110 \ 0100 \\ j_3 = i \ \& \ (j_2 - 1) = 0110 \ 0000 \\ j_4 = i \ \& \ (j_3 - 1) = 0100 \ 1100 \\ j_5 = i \ \& \ (j_4 - 1) = 0100 \ 1000 \\ j_6 = i \ \& \ (j_5 - 1) = 0100 \ 0100 \\ j_7 = i \ \& \ (j_6 - 1) = 0100 \ 0000 \\ j_8 = i \ \& \ (j_7 - 1) = 0010 \ 1100 \\ j_9 = i \ \& \ (j_8 - 1) = 0010 \ 1000 \\ j_{10} = i \ \& \ (j_9 - 1) = 0010 \ 0100 \\ j_{11} = i \ \& \ (j_{10} - 1) = 0010 \ 0000 \\ ... j1=i & (j01)=0110 1000j2=i & (j11)=0110 0100j3=i & (j21)=0110 0000j4=i & (j31)=0100 1100j5=i & (j41)=0100 1000j6=i & (j51)=0100 0100j7=i & (j61)=0100 0000j8=i & (j71)=0010 1100j9=i & (j81)=0010 1000j10=i & (j91)=0010 0100j11=i & (j101)=0010 0000...
所以它是正确的。
代码就不全放了。

Logo

集算法之大成!助力oier实现梦想!

更多推荐