题目描述

上午的训练结束了,THU ACM小组集体去吃午餐,他们一行 \(N\) 人来到了著名的十食堂。这里有两个打饭的窗口,每个窗口同一时刻只能给一个人打饭。由于每个人的口味(以及胃口)不同,所以他们要吃的菜各有不同,打饭所要花费的时间是因人而异的。另外每个人吃饭的速度也不尽相同,所以吃饭花费的时间也是可能有所不同的。

THU ACM小组的吃饭计划是这样的:先把所有的人分成两队,并安排好每队中各人的排列顺序,然后一号队伍到一号窗口去排队打饭,二号队伍到二号窗口去排队打饭。每个人打完饭后立刻开始吃,所有人都吃完饭后立刻集合去六教地下室进行下午的训练。

现在给定了每个人的打饭时间和吃饭时间,要求安排一种最佳的分队和排队方案使得所有人都吃完饭的时间尽量早。

假设THU ACM小组在时刻 \(0\) 到达十食堂,而且食堂里面没有其他吃饭的同学(只有打饭的师傅)。每个人必须而且只能被分在一个队伍里。两个窗口是并行操作互不影响的,而且每个人打饭的时间是和窗口无关的,打完饭之后立刻就开始吃饭,中间没有延迟。

现在给定N个人各自的打饭时间和吃饭时间,要求输出最佳方案下所有人吃完饭的时刻。

输入格式

第一行一个整数 \(N\) ,代表总共有 \(N\) 个人。

以下N行,每行两个整数 \(Ai\) ,\(Bi\) 。依次代表第 \(i\) 个人的打饭时间和吃饭时间。

输出格式

一个整数 \(T\) ,代表所有人吃完饭的最早时刻。

输入输出样例

输入 #1

5

2 2

7 7

1 3

6 4

8 5

输出 #1

17

说明/提示

所有输入数据均为不超过 \(200\) 的正整数。

————————————————————————————————————

这道题思维难度有点大,我是看了眼其他大佬的题解才会写的,在这里重新总结一下。

这道题看起来和经典的接水问题有相似之处,但是比接水问题多了“吃饭”这一过程以及另外的一个队列,要求我们制定出更加精妙的DP方程。

但是我们依然可以首先借鉴接水问题的贪心思路,即让吃饭速度慢的同学先打饭,最优性可以使用反证法证明。

于是我们获得了一个相对的打饭顺序(同时考虑两个队列),所以我们这样设立方程:

\(f[i][j][k]\) 表示前 \(i\) 个人在一号窗口打饭用时为 \(i\) ,在二号窗口打饭用时为 \(j\) 并且所有人都吃完饭的最短用时。

注意到这个方程的空间复杂度是 \(200^5\) 的,会MLE。

我们可以在排序之后用 \(sum\) 数组存一下前 \(i\) 个人打饭用时的前缀和,因为每人必然会在其中一个窗口打饭,所以可以用 %sum[i] - j% 算出在人们在二号窗口打饭的用时。

于是DP方程的第三维可以被优化掉,空间复杂度变成了 \(200^3\),可以接受。

如何进行状态转移?

首先考虑把当前的这个人放入一号队列的情况,

转移方程为:$ f[i][j]=min(f[i][j],max(f[i-1][j-p[i].a],j+p[i].b)) $

其中 \(max()\) 里面的两个元素分别表示当前的这个人打饭和吃饭速度都贼快,前面打饭的人还没吃完就已经打完饭并且吃完了当前的这个人吃完饭的时间是一号队列中最迟的(对此前的答案有贡献)两种情况。

由于每个变量值都是确定的,所以这两种情况要同时考虑。最后在这两值里面取最大值,再让 \(f[i][j]\) 取用时最短的大情况。

把当前人放入二号队列的情况同理,只不过是把 \(j\) 用 \(sum[i] - j\) 替换。

\(f[i][j]=min(f[i][j],max(f[i-1][j],sum[i]-j+p[i].b))\)

这样 \(f\) 数组就存了两个队列中所有人都吃完饭用时较长的一个。

代码如下:

#include <bits/stdc++.h>
#define FOR(i,s,t) for(int (i)=(s);(i)<=(t);(i)++)
#define MAXN 207
#define INF 0x3f3f3f3f
using namespace std;
struct Person { int a,b; }p[MAXN];
int n,ans=INF,sum[MAXN],f[MAXN][MAXN*MAXN];
inline bool cmp(const Person &A,const Person &B) { return A.b>B.b; }
inline int read() {
int w=0,X=0; char ch=0;
while (!isdigit(ch)) w|=ch=='-',ch=getchar();
while (isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
return w?-X:X;
}
int main() {
memset(f,INF,sizeof(f));
memset(sum,0,sizeof(sum));
n=read();
FOR(i,1,n) p[i].a=read(),p[i].b=read();
sort(p+1,p+n+1,cmp);
FOR(i,1,n) sum[i]=sum[i-1]+p[i].a;
f[1][0]=f[1][p[1].a]=p[1].a+p[1].b;
for (int i=1;i<=n;i++) {
for (int j=1;j<=sum[i];j++) {
if (j>=p[i].a) f[i][j]=min(f[i][j],max(f[i-1][j-p[i].a],j+p[i].b));
f[i][j]=min(f[i][j],max(f[i-1][j],sum[i]-j+p[i].b));
if (i==n) ans=min(ans,f[i][j]);
}
}
printf("%d",ans);
return 0;
}

Luogu2577 | [ZJOI2005]午餐 (贪心+DP)的更多相关文章

  1. luogu2577 [ZJOI2005] 午餐 贪心

    题目大意 THU ACM小组的吃饭计划是这样的:先把所有的人分成两队,并安排好每队中各人的排列顺序,然后一号队伍到一号窗口去排队打饭,二号队伍到二号窗口去排队打饭.每个人打完饭后立刻开始吃,所有人都吃 ...

  2. luogu2577/bzoj1899 午餐 (贪心+dp)

    首先,应该尽量让吃饭慢的排在前面,先按这个排个序 然后再来决定每个人到底去哪边 设f[i][j]是做到了第i个人,然后1号窗口目前的总排队时间是j,目前的最大总时间 有这个i和j的话,再预处理出前i个 ...

  3. luogu 2577 [ZJOI2005]午餐 贪心+dp

    发现让 $b$ 更大的越靠前越优,然后依次决策将每个人分给哪个窗口. 令 $f[i][j]$ 表示考虑了前 $i$ 个人,且第一个窗口的总等待时间为 $j$ 的最小总时间. 然后转移一下就好了~ #i ...

  4. 【题解】洛谷P2577 [ZJOI2005] 午餐(DP+贪心)

    次元传送门:洛谷P2577 思路 首先贪心是必须的 我们能感性地理解出吃饭慢的必须先吃饭(结合一下生活) 因此我们可以先按吃饭时间从大到小排序 然后就能自然地想到用f[i][j][k]表示前i个人在第 ...

  5. [ZJOI2005]午餐 (贪心,动态规划)

    题目描述 上午的训练结束了,THU ACM小组集体去吃午餐,他们一行N人来到了著名的十食堂.这里有两个打饭的窗口,每个窗口同一时刻只能给一个人打饭.由于每个人的口味(以及胃口)不同,所以他们要吃的菜各 ...

  6. 【BZOJ1899】[Zjoi2004]Lunch 午餐 贪心+DP

    [BZOJ1899][Zjoi2004]Lunch 午餐 Description 上午的训练结束了,THU ACM小组集体去吃午餐,他们一行N人来到了著名的十食堂.这里有两个打饭的窗口,每个窗口同一时 ...

  7. BZOJ 1899&&luogu P2577: [Zjoi2004]Lunch 午餐 贪心+DP

    贪它,再大力DP(话说觉得此题简单的真的是大佬QAQ)我想了两天...QWQ 贪心:吃饭慢的先打饭(不太会证...) DP:f[i][j]表示前i个人,在1号窗口打饭的总时间时j,的最短时间 确定i的 ...

  8. 洛谷 2577 [ZJOI2005]午餐——序列dp

    题目:https://www.luogu.org/problemnew/show/P2577 可以从只有一个窗口的角度思考出一个贪心结论.就是应当按吃饭时间(不算打饭时间)从大到小排序.这样交换相邻两 ...

  9. luogu2577 [ZJOI2005]午餐

    dp[i]表示第一队打饭时间i的最优解 #include <algorithm> #include <iostream> #include <cstring> #i ...

随机推荐

  1. 线索二叉树C++实现

    #include<iostream> #include<stdlib.h> #define maxsize 100 using namespace std; typedef s ...

  2. ceph集群部署

    最近在学习 kubernetes 过程中,想实现 pod 数据的持久化.在调研的过程中,发现 ceph 在最近几年发展火热,也有很多案例落地企业.在选型方面,个人更加倾向于社区火热的项目,Gluste ...

  3. 用命令提示符运行简单的Java程序报错

    首先用记事本写一个最简单的Java代码,我把文件保存在桌面的HelloWorld文件夹中,这里将记事本的名称改为HelloWorld.java public class HelloWorld{ pub ...

  4. 隐藏Web Shell

    隐藏Webshell $ sudo echo -e "<?=\`\$_POST[1]\`?>\r<?='PHP Test';?>" > test.ph ...

  5. React之拆分组件与组件之间的传值

    父子组件传值: 父组件向子组件传值通过向子组件TodoItem进行属性绑定(content={item}.index={index}),代码如下 getTodoItem () { return thi ...

  6. FTP服务器配置http访问(配置nginx+ftp服务器)

    一.搭建nginx服务器 先安装nginx服务器 # yum install nginx -y 启动nginx服务 # systemctl start nginx 浏览器访问:http://192.1 ...

  7. java的异常体系 及强制转换

    一,异常 1.常见的几种异常: StackOverFlow  栈溢出错误:写递归函数的时候,没有定义递归结束的条件. ArrayIndexOutofBounds   数组越界:如新new一个数组,in ...

  8. Android获取CPU编号

    /** * 获取CPU序列号 * * @return CPU序列号(16位) * 读取失败为"0000000000000000" */ private static String ...

  9. HTML连载68-形变中心点、形变中心轴

    一. 形变中心点介绍 <style> ul li { width: 100px; height: 100px; list-style: none; float:left; margin:0 ...

  10. C# convert json to datatable,convert list to datatable

    static DataTable ConvertJsonToTable(string jsonValue) { DataTable dt = (DataTable)JsonConvert.Deseri ...