前言:经过AC这道题的过程,让我明白了一点,不要过于相信您的同伴(因为他会让您的代码wa一天还不清楚为什么)。———上述观点均由StarTrek大佬背锅
题目描述
原题链接:[SDOI2009]学校食堂
小F 的学校在城市的一个偏僻角落,所有学生都只好在学校吃饭。学校有一个食堂,虽然简陋,但食堂大厨总能做出让同学们满意的菜肴。当然,不同的人口味也不一定相同,但每个人的口味都可以用一个非负整数表示。 由于人手不够,食堂每次只能为一个人做菜。做每道菜所需的时间是和前一道菜有关的,若前一道菜的对应的口味是a,这一道为b,则做这道菜所需的时间为(a or b)-(a and b),而做第一道菜是不需要计算时间的。其中,or 和and 表示整数逐位或运算及逐位与运算,C语言中对应的运算符为“|”和“&”。
学生数目相对于这个学校还是比较多的,吃饭做菜往往就会花去不少时间。因此,学校食堂偶尔会不按照大家的排队顺序做菜,以缩短总的进餐时间。
虽然同学们能够理解学校食堂的这种做法,不过每个同学还是有一定容忍度的。也就是说,队伍中的第i 个同学,最多允许紧跟他身后的Bi 个人先拿到饭菜。一旦在此之后的任意同学比当前同学先拿到饭,当前同学将会十分愤怒。因此,食堂做菜还得照顾到同学们的情绪。 现在,小F 想知道在满足所有人的容忍度这一前提下,自己的学校食堂做完这些菜最少需要多少时间。
luogu题解
先记录一下思考的过程:
读完题目。原本先想到的是贪心,就是考虑(a|b)-(a&b)的三种情况。但是,由于题目说了,大家会有一定的容忍程度。所以上述做法不可行。观察样例可得(样例真的害人 (小声bb) )这一堆人会分成几个块,也就是说我们只需要一块一块的满足就行了。并且数据范围很小,考虑Dp(Dp和贪心相伴相生),很可惜,样例是很骗人的,并且这个错让我改了一天。因为会存在,一个块的长度可能只被一个人限制了,但是如果让这个人先打饭,这个块就会变长,所以要动态处理。
具体解法:(状压DP)
滚动解决,定义状态 f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k]表示讨论到第i个人没打饭(前面i-1个人已经打完饭了,现在从i个人开始到可以容忍的结束集合的状态为j,k表示上一个打饭的人的编号。(为了方便存储,我们将k存为 p − i p-i p−i,p是上一个人的编号),那么k的范围就是[-8,7] (计算的时候会加上8防止出现负数)。有可能会有同学问为什么是-8,因为会存在0~7全都为1,并且结束位置是i,转移到i+8的时候,原本i所在的位置就变成了-8。那么我们就分成两种情况讨论:
1.如果(j&1)成立
那么就转移到下一个状态讨论:
f
[
i
+
1
]
[
j
>
>
1
]
[
k
−
1
]
=
m
i
n
(
f
[
i
+
1
]
[
j
>
>
1
]
[
k
−
1
]
,
f
[
i
]
[
j
]
[
k
]
)
f[i+1][j>>1][k-1]=min(f[i+1][j>>1][k-1],f[i][j][k])
f[i+1][j>>1][k−1]=min(f[i+1][j>>1][k−1],f[i][j][k])
1.如果(j&1)不成立
那么就枚举下一个打饭的人,并且转移状态。之前也说过了,打饭的范围会受到每一个人的容忍程度的限制,所以要动态处理。
具体详见代码。
#include<bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
const int maxn=1000+5;
int v[maxn],b[maxn],f[maxn][300][20];
int zhi(int x,int y){return (x|y)-(x&y);}
void work()
{
int n;
scanf("%d",&n);
for (int i=1;i<=n;i++)
scanf("%d%d",&v[i],&b[i]);
memset(f,0x3f,sizeof(f));
f[1][0][7]=0;
for (int i=1;i<=n;i++)
for (int j=0;j<(1<<8);j++)
for (int k=-8;k<=7;k++)
if (f[i][j][k+8]==inf) continue;
else
{
if (j&1) f[i+1][j>>1][k+7]=min(f[i+1][j>>1][k+7],f[i][j][k+8]);
else
{
int lir=inf;
for (int t=0;t<=7;t++)
if (!(j&(1<<t)))
{
if (t+i>lir) break;
lir=min(lir,t+i+b[t+i]);
if (i+k==0) f[i][j|(1<<t)][t+8]=0;
else
f[i][j|(1<<t)][t+8]=min(f[i][j|(1<<t)][t+8],f[i][j][k+8]+zhi(v[i+k],v[i+t]));
}
}
}
int ans=inf;
for (int i=-8;i<=0;i++)
ans=min(ans,f[n+1][0][i+8]);
printf("%d\n",ans);
}
int main(){
int n;
scanf("%d",&n);
for (int i=1;i<=n;i++) work();
}