题目地址:[BZOJ]1226 [SDOI2009]学校食堂Dining;
题面:
1226: [SDOI2009]学校食堂Dining
Time Limit: 10 Sec Memory Limit: 259 MB
Description
小
F
的学校在城市的一个偏僻角落,所有学生都只好在学校吃饭。学校有一个食堂,虽然简陋,但食堂大厨总能做出让同学们满意的菜肴。当然,不同的人口味也不一定相同,但每个人的口味都可以用一个非负整数表示。由于人手不够,食堂每次只能为一个人做菜。做每道菜所需的时间是和前一道菜有关的,若前一道菜的对应的口味是
Input
第一行包含一个正整数
Output
包含 C 行,每行一个整数,表示对应数据中食堂完成所有菜所需的最少时间。
Sample Input
2
5
5 2
4 1
12 0
3 3
2 2
2
5 0
4 0
Sample Output
16
1
HINT
对于第一组数据:同学
【数据规模和约定】
对于
对于
100
的数据,满足
1≤N≤1,000
,
0≤Ti≤1,000
,
0≤Bi≤7
,
1≤C≤5
。
存在
30
的数据,满足
0≤Bi≤1
。存在65%的数据,满足
0≤Bi≤5
。
存在
45
的数据,满足
0≤Ti≤130
。
题解:
首先可以发现
(a or b)−(a and b)=(a xor b)
,也就是a ^ b
.(简化运算,虽然并没有什么用的样子)
本题应该可以一眼看出是道
DP
题,通过题目中的时间计算公式是与上一个拿菜的人有关,因此数组中的下标需要保存上一个人是谁。又因为每个人都有个忍耐度,而这个忍耐度非常小,最大只有
7
,也就是说到一个人为止,前面的人都已经打好菜了,这个人与他之后的人打菜的状况数最多只有
设数组
f[i][j][k]
为前
i−1
个人都已经打好饭了,上次打饭是第
i+k
个人(有可能是之前的人,所以
k
可能是负数,所以还要加上
当前状态为:
当j & 1 == 1
的时候,说明第
i
个人已经打好饭了,当前状态其实是和
当j & 1 != 1
的时候。只能向后枚举下一次打饭的人是谁,当然只能选没打过饭的,而且在选的过程中要保证在所有人的忍耐度内。就可以转移了,计算的时候直接用a ^ b
就行了。
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
#define N 1005
#define INF 0x3f3f3f3f
inline int read(){
char ch=getchar(); int x=0,f=1;
while (ch<'0'||ch>'9') { if (ch=='-') f=-1; ch=getchar(); }
while (ch>='0'&&ch<='9') { x=x*10+ch-'0'; ch=getchar(); }
return x*f;
}
int n;
int f[N][20][256];
int w[N],b[N];
int main(int argc, char const *argv[]){
int T = read();
while(T--){
memset(f,0x3f,sizeof f);
n = read();
for(int i = 1;i <= n;++i){
w[i] = read(); b[i] = read();
}
f[1][7][0] = 0;
for(int i = 1;i <= n;++i)
for(int j = 0;j <= (1<<8)-1;++j)
for(int k = -8;k <= 7;++k)
if(f[i][k+8][j] < INF){
if(j & 1)
f[i+1][k+7][j>>1] = min(f[i+1][k+7][j>>1],f[i][k+8][j]);
else{
int p = INF;
for(int z = 0;z <= 7;++z){
if(!((j>>z) & 1)){
if(i+z > p) break;
p = min(p,i+z+b[i+z]);
f[i][z+8][j | (1<<z)] = min(f[i][z+8][j | (1<<z)],f[i][k+8][j]+(i+k?(w[i+k]^w[i+z]):0));
}
}
}
}
int ans = INF;
for(int i = 0;i <= 8;++i)
ans = min(ans,f[n+1][i][0]);
printf("%d\n", ans);
}
return 0;
}