BZOJ原题链接
洛谷原题链接
注意到(B[i])很小,考虑状压(DP)。
设(f[i][j][k])表示前(i - 1)个人已经拿到菜,第(i)个人及其后面(7)个人是否拿到菜的状态为(j),上一个拿到菜的人的编号为(i + k,-8leqslant k leqslant 7)时所用的最短时间。
然后讨论状态的转移。
- (j & 1)为真
说明第(i)个人已经拿到菜,可以直接转移至(i + 1),即$$f[i + 1][j >> 1][k - 1] = min{ f[i + 1][j >> 1][k - 1], f[i][j][k] }$$ - (j & 1)为假
此时不能转移至(i + 1),因为第(i)个人还没拿到菜,不满足定义。
于是我们可以枚举(q = 0 o 7),选出(i)后的第(q)个人去拿饭:$$f[i][j | (1 << h)][h] = min{ f[i][j | (1 << h)][h], f[i][j][k] + time(i + k, i + h) }$$
(time(x,y))表示上一个拿菜的人的编号为(x),这次为(y),则需要做菜的时间。
而在转移这种状态时,需要考虑每个人的容忍度,在循环(q)的过程中,维护一个最小的容忍度,若枚举到的人已经不被之前未拿菜的某人所容忍,那么就不需要考虑这个人与其之后的人了。
最后答案为:(min limits ^ {-8leqslant k leqslant 0} { f[n + 1][0][k] })。
因为(k)可以为负,所以在储存是要整体右移。
另外,题目中给出计算时间的公式((a | b) - (a & b)),实际上等于(a land b)。
#include<cstdio>
#include<cstring>
using namespace std;
const int N = 1010;
const int M = (1 << 8) + 10;
int f[N][M][20], T[N], B[N];
inline int re()
{
int x = 0;
char c = getchar();
bool p = 0;
for (; c < '0' || c > '9'; c = getchar())
p |= c == '-';
for (; c >= '0' && c <= '9'; c = getchar())
x = x * 10 + c - '0';
return p ? -x : x;
}
inline void ckminn(int &x, int y)
{
if (x > y)
x = y;
}
int main()
{
int i, j, k, n, m = M - 10, t, q, edr, mi;
t = re();
while (t--)
{
n = re();
for (i = 1; i <= n; i++)
{
T[i] = re();
B[i] = re();
}
memset(f, 60, sizeof(f));
f[1][0][7] = 0;
for (i = 1; i <= n; i++)
for (j = 0; j < m; j++)
for (k = -8 ; k <= 7; k++)
if (f[i][j][k + 8] < 1e8)
{
if (j & 1)
ckminn(f[i + 1][j >> 1][k + 7], f[i][j][k + 8]);
else
{
edr = 1e9;
for (q = 0; q <= 7; q++)
if (!(j & (1 << q)))
{
if (i + q > edr)
break;
ckminn(edr, i + q + B[i + q]);
ckminn(f[i][j | (1 << q)][q + 8], f[i][j][k + 8] + (i + k ? T[i + k] ^ T[i + q] : 0));
}
}
}
for (mi = 1e9, i = 0; i <= 8; i++)
ckminn(mi, f[n + 1][0][i]);
printf("%d
", mi);
}
return 0;
}