题目传送门
前言
第一次见这么新颖的 d p dp dp,竟然可以从当前枚举的 i i i 的前面或者后面转移过来,这不就有后效性了吗?
好了开玩笑
其实只要状态设计好,还是没有后效性的。
思路
状态设计
首先 B i ≤ 7 B_i \leq 7 Bi≤7,所以肯定是状压 d p dp dp,所以一定起码有两维:一维是当前枚举到的 i i i,一维是压缩后的状态 s s s(具体是什么等会说)。
然后他又说 【当前做的菜所用的时间】 还和 【前一个菜的口味】 有关系,所以需要增加一维,表示上一个打到饭的同学是 j j j。
但是这样开的话数组会炸。由于 B i ≤ 7 B_i \leq 7 Bi≤7,所以我们第三位可以用 j j j 来表示上一个打饭的同学是 i + j i + j i+j( j j j 的范围等会再说)。
现在来看 d p i , s , j dp_{i, s, j} dpi,s,j 的含义:表示 [ 1 , i − 1 ] [1, i-1] [1,i−1] 的同学 【已经全部打完饭】 ,现在枚举到第 i i i 个同学, 【他以及他身后 7 7 7 个同学】 的打饭状态是 s s s,【目前最后一个打饭的同学】 是 i + j i + j i+j,此时所用的最少时间。
j j j 的取值范围是 [ − 8 , 7 ] [-8, 7] [−8,7]。右边界的 7 7 7 很容易明白,就是同学 i i i 最多能忍受到的地方。左边界的 − 8 -8 −8 是因为:由于上个打饭的同学 【也是 [ 1 , i − 1 ] [1, i - 1] [1,i−1] 同学中最后一个打饭的】,所以第 i − 1 i - 1 i−1 个同学打饭时,他最多插到第 i − 1 − 7 = i − 8 i - 1 - 7 = i - 8 i−1−7=i−8 个同学的前面,所以左边界是 − 1 -1 −1。
为了防止数组越界,在代码实现时只需要给下标加上一个偏移量就好。
状态转移
首先要分类:第 i i i 个同学已经打过饭了(即 s & 1 = = 1 s \ \& \ 1 == 1 s & 1==1),和第 i i i 个同学还没打饭(即 s & 1 = = 0 s \ \& \ 1 == 0 s & 1==0)。
为什么这么分呢?
因为无论做哪道 d p dp dp 时,我们所要做的状态转移就是 【把第 i i i 位的状态转移到第 i + 1 i + 1 i+1 位】。
而在这道题中,只有当 s & 1 = = 1 s \ \& \ 1 == 1 s & 1==1 时,它才能往下一位转移(解释在下面)。
- 当 s & 1 = = 1 s \ \& \ 1 == 1 s & 1==1 时, d p i , s , j dp_{i, s, j} dpi,s,j 可以转移到 d p i + 1 , s > > 1 , j − 1 dp_{i + 1, s >> 1, j - 1} dpi+1,s>>1,j−1。因为 i i i 的时间花费已经被计算过了,我们要考虑的就是第 i + 1 i + 1 i+1 位及以后的学生。而且对于 i i i 来说,目前最后一个打饭同学是 i + j i + j i+j,那么对于 i + 1 i + 1 i+1 来说,目前最后一个打饭的同学是 i + 1 + j − 1 = = i + j i + 1 + j - 1 == i + j i+1+j−1==i+j,二者是等价的,因此可以转移。
转移方程:
d p i + 1 , s > > 1 , j − 1 = m i n ( d p i + 1 , s > > 1 , j − 1 , d p i , s , j ) dp_{i + 1, s >> 1, j - 1} = min(dp_{i + 1, s >> 1, j - 1}, dp_{i, s, j}) dpi+1,s>>1,j−1=min(dpi+1,s>>1,j−1,dpi,s,j) - 当 s & 1 = = 0 s \ \& \ 1 == 0 s & 1==0 时,此时无法再向 i + 1 i + 1 i+1 位转移,因为不满足规定的前 ( i + 1 ) − 1 (i + 1) - 1 (i+1)−1 位同学都打完了饭。但是我们可以转移一下第二维的状态 s s s,我们可以找到一个 [ i , i + 7 ] [i, i + 7] [i,i+7] 区间内没打饭的同学,并现在让他打饭来转移。但是需要注意一点,就是转移时看看 【有没有超过某个同学的忍耐范围】,没超过就转移。
枚举当前状态 s s s 的二进制位 k k k,若 s & ( 1 < < k ) = = 0 s \ \& \ (1 << k) == 0 s & (1<<k)==0,那么就有转移方程:
d p i , s ∣ ( 1 < < k ) , k = m i n ( d p i , s ∣ ( 1 < < k ) , k , d p i , s , j + ( T i + j ⊕ T i + k ) ) dp_{i, s | (1 << k), k} = min(dp_{i, s | (1 << k), k}, dp_{i, s, j} + (T_{i + j} \oplus T_{i + k})) dpi,s∣(1<<k),k=min(dpi,s∣(1<<k),k,dpi,s,j+(Ti+j⊕Ti+k))
边界条件
把 d p 1 , 0 , 0 dp_{1, 0, 0} dp1,0,0 设为 0 0 0,其他全设为 I N F INF INF。
答案
a n s = m i n { d p n + 1 , 0 , k } , k ∈ [ − 8 , 0 ] ans = min \{dp_{n + 1, 0, k}\}, k \in [-8, 0] ans=min{dpn+1,0,k},k∈[−8,0](之所以是 n + 1 n + 1 n+1,是因为状态设计中第一维 i i i 是表示 [ 1 , i − 1 ] [1, i - 1] [1,i−1] 中的同学全部打完饭)。
代码
#include <bits/stdc++.h>using namespace std;const int maxn = 1e3 + 7;
const int maxs = (1 << 8) + 7;
const int inf = 0x3f3f3f3f;int qr() {int x = 0, f = 1; char c = getchar();while (c < '0' || c > '9') {if (c == '-') f = -1; c = getchar();}while (c >= '0' && c <= '9') {x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}return x * f;
}int n;
int t[maxn], b[maxn];// dp[i][s][j] 表示考虑第 i 个人时
// 他以及他后面 7 个人(一共 8 个人) 的吃饭状态为 s
// 最后一个吃饭的人为 i + k 状态下的最少时间, k ∈ [-8, 7]
int dp[maxn][maxs][25];
void sol() {memset(dp, inf, sizeof(dp));memset(t, 0, sizeof(t));memset(b, 0, sizeof(b));n = qr();for (int i = 1; i <= n; ++i) t[i] = qr(), b[i] = qr();dp[1][0][-1 + 10] = 0;for (int i = 1; i <= n; ++i) {for (int s = 0; s < (1 << 8); ++s) {for (int j = -8; j <= 7; ++j) {if (dp[i][s][j + 10] == inf) continue;if (s & 1) dp[i + 1][s >> 1][j - 1 + 10] = min(dp[i + 1][s >> 1][j - 1 + 10], dp[i][s][j + 10]);else {int r = inf;for (int k = 0; k < 8; ++k) if (!(s & (1 << k))){if (i + k > r) break;r = min(r, i + k + b[i + k]);dp[i][s | (1 << k)][k + 10] = min(dp[i][s | (1 << k)][k + 10], dp[i][s][j + 10] + (i + j == 0 ? 0 : (t[i + k] ^ t[i + j])));}}}}}int ans = inf;for (int k = -8; k <= -1; ++k)ans = min(ans, dp[n + 1][0][k + 10]);printf("%d\n", ans);
}
int main() {int T = qr();while (T--) sol();return 0;
}