阿里巴巴(区间dp)

题目

【问题描述】

  在一条直线上有 n 件珠宝,已知每件珠宝的位置xi,并且第 i 件珠宝在 ti 时刻(单位:秒)就消失,问能否将所有的珠宝收集起来?如果能,求出最短时间。搜集能瞬间完成,但收集的人每秒只能移动一个单位的距离。

【输入格式】  

  若干组数据,每组数据的第一行为n,表示有n颗珠宝,接下来的n行,每行宝行两个整数:x,t,表示珠宝的位置和消失时间。

【输出格式】  

  如果有解,则输出一个整数,表示将所有珠宝收集完的最早完成时间,否则输出No solution。

【输入样例】  

5
1 3
3 1
5 8
8 19
10 15

5
1 5
2 1
3 4
4 2
5 3

【输出样例】  

11
No solution

【数据范围】  

n<=10000
0<=x,t<=10^9

注意:在t[i]时刻走到i点不算拿到宝石

分析

错误的贪心1:找一个点先往左后往后或者先往右再往左,因为可能为了t折返几次,因此贪心不对
错误的贪心2:找t最小的,很明显t足够大的时候(随便怎么走无所谓)找到的很可能不是最优解,但这个贪心是可以ac的

dp,这是个很不错的区间dp
dp面临的问题是起点和终点可能有多种情况,面对终点问题,有分析得我们可以很轻松的知道要么最后停留在左端点,要么停留在右端点,因此开两个状态即可
接下来讨论起点的问题,我们是否可以和终点一样f(i,j)表示以i为起点并且允许i>j,这样最后单独枚举起点计算答案,这样问题就和错误的贪心1一样遇到需要折返的问题则无从下手,同时不能直接写出答案还要用这种方法搜索感觉的确不满足最优子结构

那么到底该怎么做呢,在查阅了网上资料后,我发现可以直接定义:
f(i,j,0)表示区间i~j中获得所有宝藏并且停留在左端点的最小时间,f(i,j,1)停留在右端点
那么疑惑的问题就来了,如何保证第一次正好是从任意节点出发,并且其他节点总是连贯呢?
以左端点为例,我们先把区间拆成i~i+1和i+1~j,那么我们可以把这个任意的起始点放在i+1~j中,形成了一个完全相同的子问题,然后在走到左端点去,转移的时候要用到停留在i+1和停留在j的两种情况一起转移,因为他们的答案完全可能不同,
至于那个t,就只需要在单独隔离一个元素的时候考虑是否达到了期限,达到了就把值赋成inf即可。

方程答案与转移如下

f(i,j,0)表示区间i~j中获得所有宝藏并且停留在左端点,f(i,j,1)停留在右端点
ans=min(f(1,n,0),f(1,n,1)) 
i=j时,f(i,j,0)=f(i,j,1)=0; 
f(i,j,0)=min(f(i+1,j,1)+x[i+1]-x[i],f(i+1,j,0)+x[j]-x[i])
f(i,j,1)=min(f(i,j-1,0)+x[j]-x[j-1],f(i,j-1,1)+x[j]-x[i])
填表的时候需要按照主对角线填入

代码

#include<queue>
#include<cmath>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=1e4+5,inf=2e9+5;
struct data
{
    int x,t;
    friend bool operator<(data a,data b)
    {
        return a.x<b.x;
    }
}a[maxn];
int n,ans;
int d[2][maxn][2];
void solve()
{
    memset(d,0,sizeof(d));
    for(int k=1;k<n;k++)
    {
        for(int i=1;i<=n-k;i++)
        {
            int j=i+k;
            d[i&1][j][1]=min(d[(i+1)&1][j][1]+a[i+1].x-a[i].x, 
                   d[(i+1)&1][j][0]+a[j].x-a[i].x );
            if(d[i&1][j][1]>=a[i].t)
                d[i&1][j][1]=inf;

            d[i&1][j][0]=min(d[i&1][j-1][0]+a[j].x-a[j-1].x, 
                   d[i&1][j-1][1]+a[j].x-a[i].x );
            if(d[i&1][j][0]>=a[j].t)
                d[i&1][j][0]=inf;
        }
    }
    ans=min(d[1&1][n][0],d[1&1][n][1]);
    if(ans<inf)
        printf("%d\n",ans);
    else printf("No solution\n");
}
int main()
{
    freopen("in.txt","r",stdin);
    while(scanf("%d",&n)!=EOF)
    {
        for(int i=1;i<=n;i++)
        {
            scanf("%d%d",&a[i].x,&a[i].t);
        }
        sort(a+1,a+n+1);
        solve();
    }
    return 0;
}

注意
1、这里为了节省空间必须要用到填表+滚动数组,再者因为区间dp是从两边往中间收拢的,因此一般按照主对角线填表,注意如果保证i<=j,那么应该从中间往右上角填表
2、inf应该是个常见技巧,就不多说了

收获

1、分析对象的时候搞清楚主要矛盾,我们的最优值肯定是求最短时间,因此deadline可以放一放,最后想想办法就可以搞定
2、在感觉到一道题要用dp的时候但是明显发现这道题的起点和终点有多个,那么就找最有代表性的点作为状态,可以有多个
3、区间dp不一定要多个区间合并,也可以用端点缩小法
4、见代码注意的程序收获

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值