zoukankan      html  css  js  c++  java
  • ZJOI2005午餐

    描述

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

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

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

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

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

    输入

    第一行一个整数N,代表总共有N个人。 以下N行,每行两个整数 Ai,Bi。依次代表第i个人的打饭时间和吃饭时间。

    输出

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

    样例输入[复制]
    5
    2 2
    7 7
    1 3
    6 4
    8 5
    样例输出[复制]
    17
    提示

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

    样例说明 方案如下: 窗口1: 窗口2: 7 7 1 3 6 4 8 5 2 2

    标签
    ZJOI2005
     
     
    这道题其实一看我们知道要结合贪心,这个道理其实是跟烹调方案是一样的
    烹调方案告诉我们贪心应该从两个相邻的元素来入手
    那么这道题里面有一个条件是吃饭时间长的人应该先排在前面
    证明:假设有两个人吃饭时间是ai,bi,aj,bj其中bi>bj
    如果是i先打饭的话,最长的时间就是ai+aj+bj
    反之就是ai+aj+bi
    显然进行比较的话会发现前者更优,得证。进行排序
    想看dp方程的直接翻到第二种猜想下面吧,接下来一点内容讲我自己总结出来的一点点思路
    然后就是一个dp过程
    我们找出题目中的几个状态:12号窗口人数,12号当前所需排队+吃饭的最大值(好像没有别的了)
    那么设状态就有这么一个问题,首先很显然f[i]一个状态是啥也刻画不了的
    在不考虑空间大小的时候在刻画两个队伍的时候肯定是要考虑各自的时间的,显然用f[i][j][k]来刻画。
    那么这里就引入一个问题:到底状态是记录人数,还是最大的排队时间数+吃饭数,还是记录排队到当前的排队时间数(非吃饭)。结果又该记录什么东西?
     
    首先我们来考虑一下如果我们设人数为状态的话,那么有一维是多余的,压缩成两维f[i][j]代表i个人第一个窗口排j个人,
    那么就有一个问题,因为只能存储一个值就会发现记录第一个会丢失第二个窗口的时间,你会发现无法刻画最大值和每一个窗口现在的状态
    第一种猜想pass
     
    考虑我们按第二种方式构建dp,显然  f[i][j][k]   刻画前   i   个人,第一个窗口最大当前排队吃饭最大时间是    j    ,第二个是    k     ,然后往下转移就是当前人到第一个或者是第二个窗口然后更新。
    但我们忽略了这个状态到底存储什么值的问题,因为题目要求的值已经是维度了,而且如果你存当前的排队时间貌似没有dp的大小转移的性质了,就成鸡肋了
    第二种猜想pass
     
    那么我们只能考虑最后一种,很显然还是f[i][j][k], 刻画前   i   个人,第一个窗口当前排队时间是    j    ,第二个是    k     ,然后我们这个dp存什么值?显然就是最大的排队+吃饭的时间
    这样做有什么好处?吃饭的时间维度里面进行了描述,转移到下一个人加上他自己的时间就可以了,这样就可以进行状态转移了
    考虑第i个人的转移,显然是到第一个窗口或者第二个窗口,dp方程就出来了
    dp[i][j][k]=min(dp[i][j][k],max(dp[i-1][j-e[i].a][k],j+e[i].b));
    //这是第一个窗口
    dp[i][j][k]=min(dp[i][j][k],max(dp[i-1][j][k-e[i].a],k+e[i].b));
    //第二个

    为什么我们要max,因为我们记录最大值,这个人排在这里可能比原来要长,取min不说了

    但这个时候我们看一下会发现空间爆炸了怎么办???????借鉴我之前的dp经验肯定是要压一波空间了

    滚动数组+1,如果你滚掉第一维理论上可以,但是 j , k 都是4e4级别的,舍掉

    只能滚掉 j 或者 k ,假设滚掉第二个窗口 k ,接着就会发现那个k+e[i].b怎么办????

    仔细看下其实发现 j + k 是前 i 个人排队时间的前缀和,高高兴兴滚掉

    然后为什么可以正序,因为i由i-1滚过来,不会重叠

    over!

    总结:一道贪心+dp的好题目,贪心我竟然想偏了。。。。唉,还是太菜了QAQ
    dp说实话应该与贪心隔离开来思考,这样就不会是一团糟了,很多题的维度就是状态,有时候还是前缀甚者是后缀,逐个尝试分析吧
    太菜了555
    code:
     1 #include<iostream>
     2 #include<cstdio>
     3 #include<algorithm>
     4 #include<cstring> 
     5 #define N 1004
     6 using namespace std;
     7 struct node{
     8     int a,b;
     9 }e[N];
    10 bool cmp(node a,node b){
    11     return a.b>b.b;
    12 }
    13 int sum[N];
    14 int f[205][40005];
    15 int main(){
    16     int n;
    17     cin>>n;
    18     for(int i=1;i<=n;i++)
    19         cin>>e[i].a>>e[i].b;
    20     sort(e+1,e+n+1,cmp);
    21     for(int i=1;i<=n;i++)
    22         sum[i]=sum[i-1]+e[i].a;
    23     memset(f,0x3f3f3f3f,sizeof f);
    24     f[0][0]=0;
    25     for(int i=1;i<=n;i++){
    26         for(int j=0;j<=sum[i];j++){
    27             if(j>=e[i].a)f[i][j]=min(f[i][j],max(f[i-1][j-e[i].a],j+e[i].b));
    28             f[i][j]=min(f[i][j],max(f[i-1][j],sum[i]-j+e[i].b));
    29         }
    30     }
    31     int ans=2147483647;
    32     for(int i=0;i<=sum[n];i++)
    33     ans=min(ans,f[n][i]);
    34     cout<<ans;
    35     return 0;
    36 }

    额,还要补前面的题解,慢慢来

  • 相关阅读:
    SpringMVC是什么?
    SpringMVC工作原理
    SQL给字段加上统一的某个字符
    把数据库里的标签去掉
    Windows通过DOS命令进入MYSQL的方法
    mysql添加字段
    sqlserver查询最接近的记录
    LIST 排序
    Tsk4.5异步
    认识和使用Task
  • 原文地址:https://www.cnblogs.com/saionjisekai/p/9683596.html
Copyright © 2011-2022 走看看