详解[USACO04OPEN] Turning in Homework(区间DP经典题)(洛谷P2339)

题目描述

贝茜有 C ( 1≤C≤1000 )门科目的作业要上交,之后她要去坐巴士和奶牛同学回家。

每门科目的老师所在的教室排列在一条长为 H( 1≤H≤1000 )的走廊上,他们只在课后接收作业,交作业不需要时间。贝茜现在在位置0,她会告诉你每个教室所在的位置,以及走廊出口的位置。她每走1个单位的路程,就要用1秒。她希望你计算最快多久以后她能交完作业并到达出口。

输入格式

第一行:三个整数 C,H 和 B,1≤C≤1000,1≤H≤1000,0≤B≤H

第二行到 C+1 行:第 i+1 行有两个整数 Xi​ 和 Ti​,0≤Xi≤H,0≤Ti≤10000。

B 表示车站位置。

Xi​ 表示第 ii 份作业应该在 Xi​ 这个位置交。

Ti​ 表示这个位置(教室)的这个科目老师(也就是收这份作业的老师)下课的时间。

输出格式

单个整数,表示贝西交完作业后走到车站的最短时间

样例 #1

样例输入 #1

4 10 3
8 9
4 21
3 16
8 12

样例输出 #1

22

提示

走到坐标 8 处,第 9 分钟交一本作业,等到第 12 分钟时,交另一本作业。再走到坐标 4 处交作业,最后走到坐标 3 处,交最后一本作业,此地就是车站所在位置,共用时 22 分钟

思路

其实就是一道很裸的区间dp + 贪心,我们分成两部分考虑

一:贪心

最佳策略应该是由下面两种状态转移过来的

  1. 向左或向右移动到 下一个教室 交作业

  2. 从一个端点移动到另一个端点

如果先去中间某个教室之后必然还要走到i教室和j教室,那么不如先去i教室或者j教室,之后去另一端的教室时必然会经过中间的教室交作业。

二:区间dp

先把教室按照x轴上的坐标排序

struct ff
{
  int x,t;
} a[10000001];
bool cmp(ff xx,ff yy)
{
  return xx.x < yy.x;
}

  cin>>c>>h>>b;
  for(int i = 1; i <= c; i++) cin>>a[i].x>>a[i].t;
  sort(a + 1,a + 1 + c,cmp);

我们用dp[i][j][0/1]表示区间i~j还未提交作业,下一步将提交i或j点的作业所需的最短时间。

这样就能写出状态转移方程

for(int i = 1; i <= c; i++)
    for(int j = c; j >= i; j--)
    {
      dp[i][j][0] = min(dp[i][j][0],max(dp[i - 1][j][0] + a[i].x - a[i - 1].x,a[i].t));
      dp[i][j][0] = min(dp[i][j][0],max(dp[i][j + 1][1] + a[j + 1].x - a[i].x,a[i].t));
      dp[i][j][1] = min(dp[i][j][1],max(dp[i][j + 1][1] + a[j + 1].x - a[j].x,a[j].t));
      dp[i][j][1] = min(dp[i][j][1],max(dp[i - 1][j][0] + a[j].x - a[i - 1].x,a[j].t));
    }

注意:这里j需要倒序枚举,因为dp[i][j][1 / 0]是由dp[i / i - 1][j / j + 1][0 / 1]转移而来的。

  for(int i = 1; i <= c; i++)
    for(int j = c; j >= i; j--)

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
int c,h,b,dp[2001][2001][2],ans = 1e9;
/*
dp[i][j][0/1]表示区间i~j还未提交作业
下一步将提交i或j点的作业所需的最短时间
*/
struct ff
{
  int x,t;
} a[10000001];
bool cmp(ff xx,ff yy)
{
  return xx.x < yy.x;
}
signed main()
{
  cin>>c>>h>>b;
  for(int i = 1; i <= c; i++) cin>>a[i].x>>a[i].t;
  sort(a + 1,a + 1 + c,cmp);
  memset(dp,0x3f,sizeof(dp));
  dp[1][c][0] = max(a[1].x,a[1].t);
  dp[1][c][1] = max(a[c].x,a[c].t);
  for(int i = 1; i <= c; i++)
    for(int j = c; j >= i; j--)
    {
      dp[i][j][0] = min(dp[i][j][0],max(dp[i - 1][j][0] + a[i].x - a[i - 1].x,a[i].t));
      dp[i][j][0] = min(dp[i][j][0],max(dp[i][j + 1][1] + a[j + 1].x - a[i].x,a[i].t));
      dp[i][j][1] = min(dp[i][j][1],max(dp[i][j + 1][1] + a[j + 1].x - a[j].x,a[j].t));
      dp[i][j][1] = min(dp[i][j][1],max(dp[i - 1][j][0] + a[j].x - a[i - 1].x,a[j].t));
    }
  for(int i = 1; i <= c; i++)
  {
    int t = min(dp[i][i][0],dp[i][i][1]) + abs(a[i].x - b);
    ans = min(ans,t);
  }
  cout<<ans;
  return 0;
}

怎么样?听懂了吗?如果听懂了就请点赞收藏加关注支持一下吧!您的支持是我继续坚持下去的动力!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值