题解:洛谷 P2157 [SDOI2009] 学校食堂

题意

\(n\) 个学生在食堂窗口排成一列打饭,他们每个人都有一个想吃的口味 \(t_i\)。食堂每次只能为一个人做菜,但是必须满足学生的口味。食堂做菜花费的时间是与上一道菜绑定的,假设某道菜口味为 \(a\),上一道菜为 \(b\),那么做这道菜花费时间为 \((a\operatorname{or}b)-(a\operatorname{and}b)\),做第一道菜不计算时间。为了加快做饭时间,食堂不会严格按照排队顺序做菜,也就是说食堂有可能会给队列中第二、第三、甚至更靠后的同学做菜,但是同学也是有忍耐限度的,每个同学最多只允许紧跟他后面的 \(b_i\) 个同学先拿菜,否则同学就会闹事。在不让同学闹事的情况下,求给所有同学做完菜的最短时间。

\(b_i\le7\)

思路

看到 \(b_i\) 的范围,想到状压。

我们把 \(b_i\) 先看作 \(7\),那么对于编号为 \(i\) 的同学,他之后的 \(7\) 个人必定在忍耐限度内,那么我们可以把这 \(8\) 个人的打饭状态压缩成一个二进制数,即总状态数为 \(2^8\)。根据做菜时间的计算公式,我们还要记录 \(i\) 前一个打饭的人是谁,思考发现,\(i\) 之后的 \(7\) 个人有可能是他前一个打饭的人,而 \(i\) 也可能在 \(i\) 之前的 \(7\) 个人的忍耐限度内,所以这 \(7\) 个人也可能先打饭。

如果思考到这里,就会出错。我们发现当第 \(i-8\) 个人打完饭后,轮到 \(i-7\) 了,但是此时 \(i-7\) 的忍耐范围内可能会有 \(i\),那么 \(i\) 就可能在下一个打饭,所以此时 \(i\) 的前一个人为 \(i-8\),即一共有 \(15\) 个人可能在 \(i\) 前面打饭,加上他自己就是 \(16\) 个人。

由于我们要枚举 \(i\),所以还有一维数组。那么我们状态记录的方式就想出来了:用 \(f_{i,j,k}\) 表示当前枚举到 \(i\),他前面的所有人都打完饭了,此时 \(i\) 和他后面 \(7\) 个人的状态为 \(j\),上一个打饭的人为 \(k\)。但是如果记录上一个打饭人的编号就会爆空间,于是想到可以记录上一个打饭的人到 \(i\) 的距离 \(k-i\),由于 \(k-i\) 可能为负,在记录下标时,我们将 \(k\) 统一 \(+8\)。于是 \(k\) 的意义更改为,上一个打饭人到 \(i\) 的距离。

接下来思考如何转移。对于同学 \(i\) 和状态 \(j\),如果 \(i\) 已经打完饭了,则有 \(j\&1=1\),此时我们就可以进行到 \(i+1\) 同学了,而由于 \(i\) 的状态在 \(j\) 的第一位,我们要将 \(j\) 右移一位。对于 \(k\),设上个打饭的同学编号是 \(st\),则 \(k=st-i\),而此时 \(k=st-(i+1)=st-i-1\),所以 \(k\) 要减一。在不考虑记录下标时统一加的 \(8\) 的情况下,此时的状态转移方程为 \(f_{i+1,j>>1,k-1}=\min(f_{i+1,j>>1,k-1})\)

如果 \(j\&1=0\),那么 \(i\) 没有打饭,我们就不能进行到下一个同学。此时我们可以考虑让后面的同学先打饭,也就是说可以再来一重循环,枚举 \(i\) 后面的同学 \(w\) 先打饭的情况,但是为了防止超过前面同学的忍耐度,我们用一个 \(s\),在枚举的同时记录从 \(i\) 开始的同学的最大能忍耐的位置的最小值,一旦枚举过了这个最小值,就有同学无法忍耐,直接break掉。

此时我们就确定了上一个打饭的人为 \(w\),那么状态 \(j\) 也要更改。而花费的时间 \(time=(t_k|t_w)-(t_k\& t_w)\) 也可以计算。则此时的状态转移方程为 \(f_{i,j|2^w,w}=min(f_{i,j,k}+time)\)

注意不要越界。

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e3+10,inf=0x3f3f3f3f;
int C;
int n,ans=inf;
int t[maxn],b[maxn];
int f[maxn][266][26];//k+8
int main(){
	cin>>C;
	while(C--){
		memset(t,0,sizeof(t));
		memset(b,0,sizeof(b));
		memset(f,inf,sizeof(f));
		cin>>n;
		for(int i=1;i<=n;i++)cin>>t[i]>>b[i];
		f[1][0][7]=0;
		for(int i=1;i<=n;i++){
			for(int j=0;j<=255;j++){
				for(int k=-8;k<=7;k++){
					if(f[i][j][k+8]==inf)continue;
					if(j&1)f[i+1][j>>1][k+7]=min(f[i][j][k+8],f[i+1][j>>1][k+7]);
					else{
						int s=inf;
						for(int w=0;w<=7;w++){
							if((j>>w)&1)continue;
							if(w+i>s)break;
							s=min(s,w+i+b[w+i]);
							if(i+k>0)f[i][j|(1<<w)][w+8]=min(f[i][j|(1<<w)][w+8],f[i][j][k+8]+(t[i+k]|t[i+w])-(t[i+k]&t[i+w]));
							else f[i][j|(1<<w)][w+8]=min(f[i][j|(1<<w)][w+8],f[i][j][k+8]);
						}	
					}
				}
			}
		}
		ans=inf;
		for(int i=-8;i<=-1;i++)ans=min(ans,f[n+1][0][i+8]);
		cout<<ans<<'\n'; 
	}
	return 0;
}
posted @ 2025-08-13 17:02  ImNot6Dora  阅读(5)  评论(0)    收藏  举报