SDOI 2009 学校食堂

本文探讨了一个关于学校食堂如何优化打饭流程的问题,通过动态规划的方法,在考虑学生容忍度的前提下,寻找完成所有打饭任务所需的最短时间。
P1546学校食堂

描述

小F的学校在城市的一个偏僻角落,所有学生都只好在学校吃饭。学校有一个食堂,虽然简陋,但食堂大厨总能做出让同学们满意的菜肴。当然,不同的人口味也不一定相同,但每个人的口味都可以用一个非负整数表示。

由于人手不够,食堂每次只能为一个人做菜。做每道菜所需的时间是和前一道菜有关的,若前一道菜的对应的口味是a,这一道为b,则做这道菜所需的时间为(a or b)-(a and b),而做第一道菜是不需要计算时间的。其中,or和and表示整数逐位或运算及逐位与运算,C语言中对应的运算符为”|”和”&”。

学生数目相对于这个学校还是比较多的,吃饭做菜往往就会花去不少时间。因此,学校食堂偶尔会不按照大家的排队顺序做菜,以缩短总的进餐时间。

虽然同学们能够理解学校食堂的这种做法,不过每个同学还是有一定容忍度的。也就是说,队伍中的第i个同学,最多允许紧跟他身后的Bi个人先拿到饭菜。一旦在此之后的任意同学比当前同学先拿到饭,当前同学将会十分愤怒。因此,食堂做菜还得照顾到同学们的情绪。

现在,小F想知道在满足所有人的容忍度这一前提下,自己的学校食堂做完所有菜最少需要多少时间。

格式

输入格式

输入文件dining.in的第一行包含一个正整数C,表示测试点的数据组数。

每组数据的第一行包含一个正整数N,表示同学数。

每组数据的第二行起共N行,每行包含两个用空格分隔的非负整数Ti和Bi,表示按队伍顺序从前往后的每个同学所需的菜的口味和这个同学的忍受度。

每组数据之间没有多余空行。

输出格式

输出文件dining.out包含C行,每行一个整数,表示对应数据中食堂完成所有菜所需的最少时间。

样例1

样例输入1

2
5
5 2
4 1
12 0
3 3
2 2
2
5 0
4 0

样例输出1

16
1

限制

各个测试点1s

提示

【样例说明】
对于第一组数据:
同学1允许同学2或同学3在他之前拿到菜;同学2允许同学3在他之前拿到菜;同学3比较小气,他必须比他后面的同学先拿菜……
一种最优的方案是按同学3、同学2、同学1、同学4、同学5做菜,每道菜所需的时间分别是0、8、1、6及1。

【数据规模和约定】
对于30%的数据,满足1 ≤ N ≤ 20。

对于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。


观察b数组的大小范围就不难想到动规,但是实际上没有一个好的状态是很难过这道题的

经过分析我们知道:

假如第i个人还没有打饭,则i+7绝不可能打过饭了,因为第i个人的最大容忍度不会超过7;

如果第i个人之前全部取完了,则目前为止最后一个打饭的人不可能是i-8之前的人,因为i-1不可能先于i-9及之前的人打饭。

第i个人最多与后面7个人产生关系,所以可以用一个集合S来记录i和i后面7个人中已经打过饭的人

所以设计状态:
f [i][S][k]表示前i-1个人都打过饭了,i和i之后的7个人的已打饭者的集合S,上一个打饭的人与i的相对位置是k(可能为负),在这种情况下的最优代价。


因此这里我们就有两种情况需要讨论

1.如果第i个人没有打饭,那么状态f(i,s,k)就可以转化为:f(i+1,s>>1,k-1),相当于将整体往右平移一个单位

2.如果第i个人已经打了饭,那么第i个人就可以直接转化到i+1~i+7的任意一个人j,前提是j号同学没有打饭

即f(i,s|(1<<q),q)=min(f(i,s|(1<<q),q),f(i,s,k)+Cost(i+k,i+q))

具体细节看代码

#include<cstdio>
#include<iostream>
#define f(xx,yy,zz) (g[xx][yy][zz+8])
using namespace std;
const int maxn=1005,inf=1e9;
inline void _read(int &x){
    char t=getchar();bool sign=true;
    while(t<'0'||t>'9')
    {if(t=='-')sign=false;t=getchar();}
    for(x=0;t>='0'&&t<='9';t=getchar())x=x*10+t-'0';
    if(!sign)x=-x;
}
int n,c,b[maxn],t[maxn],all,g[maxn][300][20];
int Cost(int x,int y){return x==0?0:t[x]^t[y];}
int main(){
	_read(c);
	all=(1<<8)-1;
	while(c--){
		_read(n);
		int i,j,k,q,ans=inf;
		for(i=1;i<=n;i++){
			_read(t[i]);
			_read(b[i]);
		}
		for(i=1;i<=n+1;i++)
		    for(j=0;j<=all;j++)
		        for(k=-8;k<8;k++)f(i,j,k)=inf;
		f(1,0,-1)=0;
		for(i=1;i<=n;i++)
		    for(j=0;j<=all;j++)
		        for(k=-8;k<8;k++)
		            if(f(i,j,k)<inf)
		                if(j&1)f(i+1,j>>1,k-1)=min(f(i+1,j>>1,k-1),f(i,j,k));
		                else {
		                	int lim=inf;//lim相当于向右延伸的最大限制,更新i+1~i+7号人中能忍受的最小值
		                	for(q=0;q<=7;q++)
		                	    if(!(j&(1<<q))){
		                	    	if(i+q>lim)break;
		                	    	lim=min(lim,i+q+b[i+q]);
									f(i,j|(1<<q),q)=min(f(i,j|(1<<q),q),f(i,j,k)+Cost(i+k,i+q));
								}
						}
		for(i=-8;i<8;i++)ans=min(ans,f(n+1,0,i));
		printf("%d\n",ans);
	}
} 



评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值