卧薪尝胆,厚积薄发。
SDOI2009 学校食堂
Date: Wed May 23 17:42:23 CST 2018 In Category: NoCategory

Description:

食堂中每个人的菜有一个值 $t[i]$ ,为他做菜所花的时间为 $t[i] \wedge t[j]$ , $j$ 是做的上一道菜的值,第 $i$ 个人会允许 $b[i]$ 个他之后的人比他先打饭,求使所有人打完饭的最短时间
$ 1 \le N \le 1000 $ $ 0 \le B_i \le7 $

Solution:

$B_i$ 很小,只有 $7$ ,用状压来表示吃饭的情况
$ f[i][j][k] $ 表示到了第 $i$ 个人,前 $i-1$ 个人已经吃完饭, $i$ 及 $i$ 之后的人的吃饭情况为 $j$ , $i$ 上一个吃饭的人与他的相对位置是 $k$
初值 $f[1][0][-1] = 0$ ,其余的都赋成 $INF$
用顺推法:
1、若 $j\&1==1$ ,前 $i$ 个人已经吃完饭,可以向后转移, $j$ 向右移了一位( $i+k=(i+1)+(k-1)$ ) $$f[i][j][k] \to f[i+1][j>>1][k-1]$ $
2、若 $j\&1$ $ != 1$ ,第 $i$ 个人还没吃饭,那就在 $j$ 中选一个没有吃饭的(包括他自己)让他吃饭
$f[i][j][k]+calc(i+k,i + p)\to f[i][j|(1<<p)][p] (calc(i,j) = (i?t[i]\wedge t[j] : 0))$
注意第二种转移的合法性,如果要让 $i+p$ 吃饭,则 $i\dots i+p-1$ 都要同意才行,从 $0$ 开始枚举,维护一个当前合法的右边界 $rig$ ,如果 $i+p>rig$ 则 $break$ ,注意只有 $j$ 中没有吃饭的才能更新 $f$ 和 $rig$ 。

Code:


#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
#define INF 0x3f3f3f3f
int testcases;
int n;
#define MAXN 1010
#define MAXB 8
int F[MAXN][1 << MAXB][MAXB * 2];
int t[MAXN],b[MAXN];
int calc(int x,int y)
{
if(x <= 0)return 0;
else return (t[x] ^ t[y]);
}
#define f(a,b,c) (F[a][b][c + 8])
void work()
{
scanf("%d",&n);
for(int i = 1;i <= n;++i)
{
scanf("%d%d",&t[i],&b[i]);
}
memset(F,INF,sizeof(F));
f(1,0,-1) = 0;
for(int i = 1;i <= n + 1;++i)
{
for(int j = 0;j < (1 << 8);++j)
{
for(int k = -8;k <= 7;++k)
{
if(f(i,j,k) >= INF)continue;
if(j & 1)
{
f(i + 1,j >> 1,k - 1) = min(f(i + 1,j >> 1,k - 1),f(i,j,k));
}
else
{
int rig = INF;
for(int l = 0;l <= 7;++l)
{
if(!(j & (1 << l)))
{
if(i + l > rig)break;
rig = min(rig,i + l + b[i + l]);
f(i,j | (1 << l),l) = min(f(i,j | (1 << l),l),f(i,j,k) + calc(i + k,i + l));
}
}
}
}
}
}
int ans = INF;
for(int k = -8;k <= -1;++k)
{
ans = min(ans,f(n + 1,0,k));
}
cout << ans << endl;
return;
}
int main()
{
scanf("%d",&testcases);
for(int i = 1;i <= testcases;++i)work();
return 0;
}
In tag: DP-状压DP
Copyright © 2020 wjh15101051
ღゝ◡╹)ノ♡