[SDOI2009]学校食堂

前言:经过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 pi,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][k1]=min(f[i+1][j>>1][k1],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();
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值