2015-2016 ACM-ICPC, NEERC, Northern Subregional Contest Problem J 【二分+DP+单调队列】

题目链接

题意

有n个地铁站,全部成线性排列,有n-1种地铁票,第i种地铁票的价格为p_i,并且能坐i站(也就是在第k个站能够到达[k-i,k+i]中的站)。现在想从起点站坐到终点站,地铁在相邻两个站之间运行花费1s(这里原文是“get from a stop to the next one in just one minute.”有歧义,坑了好久),给出在每个站出来又进去花费的时间,并且从一个站出来又进去后同一张票又可以重新使用,现给定最大的时间花费,求能够到达终点站的票中最便宜的价格。

分析

先考虑这样一个问题,假设固定一次最多能坐到的距离,求到终点的最短时间。很容易想到这样的转移方程:
(令dp[i]为到第i个站花费的最少时间,d[i]是在该站上下所花时间,r是当前票能够坐的距离)
dp[i]=dp[x]+d[i]+(ix)
其中 dp[x]=min{dp[ir]dp[i1]}

同时考虑到,我们始终是要从起始站坐到终点站的,又因为往回坐始终不是最优的选择,因此地铁运行所带来的时间花费始终是n-1,因此我们在状态转移的时候不考虑它,而在最后时间加上n-1即可

dp[i]=min{dp[ir]dp[i1]}+d[i]
最终时间为 dp[n1]+n1

对于min{dp[ir]dp[i1]},可以用单调队列优化或者直接用线段树,但单调队列在时间上会更优一些。

那么再考虑买哪一种票,首先能够走得更远但价格便宜的票肯定更优,于是先处理一下,去掉价格贵但走不远的票。这样处理后,所有票都会是按照价格递增,距离递增的顺序出现,二分枚举答案即可。

时间复杂度: O(nlog(n))(单调队列) O(nlog2(n)) (线段树)

AC代码

//2015-2016 ACM-ICPC, NEERC, Northern Subregional Contest Problem J. Journey to the “The World’s Start”
//AC 2016-9-11 15:02:46
//DP, Binary Search, Monotonic Queue
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cctype>
#include <cstdlib>
#include <cstring>
#include <vector>
#include <set>
#include <string>
#include <map>
#include <queue>
#include <deque>
#include <list>
#include <sstream>
#include <stack>
using namespace std;

#define cls(x) memset(x,0,sizeof x)
#define inf(x) memset(x,0x3f,sizeof x)
#define neg(x) memset(x,-1,sizeof x)
#define ninf(x) memset(x,0xc0,sizeof x)
#define st0(x) memset(x,false,sizeof x)
#define st1(x) memset(x,true,sizeof x)
#define INF 0x3f3f3f3f
#define lowbit(x) x&(-x)
#define input(x) scanf("%d",&(x))
#define inputt(x,y) scanf("%d %d",&(x),&(y))
#define bug cout<<"here"<<endl;
//#pragma comment(linker, "/STACK:1024000000,1024000000")//stack overflow
#define debug

const int maxn=1000100;
struct autoque
{
    long long que[maxn];
    int index[maxn];
    int l;
    int r;
    int k;
    int cur;
    autoque():l(0),r(0),k(0),cur(0){}
    autoque(int kk):l(0),r(0),k(kk),cur(0)
    {
        memset(que,0,sizeof que);
        return;
    }
    void reset(int kk)
    {
        l=0;r=0;k=kk;cur=0;
        memset(que,0,sizeof que);
    }
    long long front()
    {
        while(index[l]<cur-k)
            ++l;
        return que[l];
    }
    void insert(long long x)
    {
        if(l==r)
        {
            que[r]=x;
            index[r++]=cur++;
            return;
        }
        int ll=l,rr=r;
        int mid;
        while(rr>=ll)
        {
            mid=(ll+rr)/2;
            if(rr==ll)
                break;
            if(x==que[mid])
                break;
            if(x<que[mid])
                ll=mid+1;
            if(x>que[mid])
                rr=mid;
        }
        que[mid]=x;
        index[mid]=cur++;
        r=mid+1;
        return;
    }
}que;

int n;
long long t;
vector<pair<int,int> > p;
int d[50500];
long long dp[50500];

bool valid(int x)
{
    if(x==-1) return 0;
    dp[0]=0;
    que.reset(p[x].first);
    for(int i=1;i<n;++i)
    {
        que.insert(-dp[i-1]);
        dp[i]=-que.front()+d[i];
    }
    return dp[n-1]<=t;
}

int main()
{
    //ios::sync_with_stdio(false);
    //cin.tie(0);
    #ifdef debug
        freopen("journey.in","r",stdin);
        freopen("journey.out","w",stdout);
    #endif
    //IO
    cin>>n>>t;
    t-=(n-1);
    pair<int,int> pri;
    for(int i=0;i<n-1;++i)
    {
        input(pri.second);
        pri.first=i+1;
        while(p.size()&&p.back().second>=pri.second)
            p.pop_back();
        p.push_back(pri);
    }
    for(int i=1;i<n-1;++i)
        input(d[i]);
    d[n-1]=0;
    int ans=INF;
    int l=-1,r=p.size()-1,mid=(l+r)>>1;
    while(l<r-1)
    {
        mid=(l+r)>>1;
        if(valid(mid))
            r=mid;
        else
            l=mid;
    }
    cout<<p[r].second<<endl;
    return 0;
}
原文地址:https://www.cnblogs.com/DrCarlluo/p/6580589.html