【题解】P9521 [JOISC 2022] 京都观光

P9521 [JOISC 2022] 京都观光

题意

你在 \(n\times m\) 的网格上,你需要从 \((1,1)\) 出发,走到 \((n,m)\),只能向下或者向右走。

给定两个长度为 \(n\) 的正整数序列 \(a,b\)

设你当前位于 \((x,y)\):

  • 走到 \((x,y+1)\) 的需要消耗的费用为 \(a_x\)

  • 走到 \((x+1,y)\) 的需要消耗的费用为 \(b_y\)

求出走到 \((n,m)\) 的最小花费。

\(n,m\le 10^5\)

题解

知识点:凸包。

好深刻的凸优化,提供一个没有非常严谨但易于理解的思路。

考虑路径的一个极小局部,设当前在 \((x_1,y_1)\),要往右下走到 \((x_2,y_2)\),满足 \(x_2>x_1,y_2>y_1\)

那么分为两种走法:

  • \((x_1,y_1)\to (x_1,y_2) \to (x_2,y_2)\)(先右后下)。

  • \((x_1,y_1)\to (x_2,y_1) \to (x_2,y_2)\)(先下后右)。

因为考虑的是路径的一个极小局部的情况,所以在这里只分为两种走法。

令前者(先右后下)比后者(先下后右)花费更小,则有:

\[(y_2-y_1)a_{x_1}+(x_2-x_1)b_{y_2}<(x_2-x_1)b_{y_1}+(y_2-y_1)a_{x_2} \]

化简移项得到:

\[\frac{a_{x_2}-a_{x_1}}{x_2-x_1}>\frac{b_{y_2}-b_{y_1}}{y_2-y_1} \]

这是一个斜率的形式。

那么在最优路径中,每段路径的斜率变化都是单调递减的。

那么从 \((1,1)\) 出发,每次走斜率较小的一边即可。

注意到,对于一行或列,如果有 \(i<j<k\),满足 \(i\to j\) 的斜率大于 \(j\to k\),显然可以直接扔掉 \(j\),所以可以先用单调栈预处理出他们的下凸壳(斜率单调递增)。

#include<bits/stdc++.h>
using namespace std;

#define rep(i,l,r) for(int i=(l);i<=(r);++i)
#define per(i,l,r) for(int i=(r);i>=(l);--i)
#define pr pair<int,int>
#define fi first
#define se second
#define pb push_back
#define all(x) (x).begin(),(x).end()
#define sz(x) (int)(x).size()
#define bg(x) (x).begin()
#define ed(x) (x).end()

#define N 102505
#define int long long

int n,m,a[N],b[N];
int sa[N],sb[N],ta,tb;

inline bool chk(int x,int y,int xx,int yy){
    if(!x){
        return 0;
    }
    if(!xx){
        return 1;
    }

    return y*xx<=yy*x;
}

signed main(){
    // freopen(".in","r",stdin);
    // freopen(".out","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);

    cin>>n>>m;

    rep(i,1,n){
        cin>>a[i];
    }
    rep(i,1,m){
        cin>>b[i];
    }

    sa[++ta]=1;
    sb[++tb]=1;

    rep(i,2,n){
        while(ta>1&&chk(i-sa[ta],a[i]-a[sa[ta]],sa[ta]-sa[ta-1],a[sa[ta]]-a[sa[ta-1]])){
            ta--;
        }
        sa[++ta]=i;
    }

    rep(i,2,m){
        while(tb>1&&chk(i-sb[tb],b[i]-b[sb[tb]],sb[tb]-sb[tb-1],b[sb[tb]]-b[sb[tb-1]])){
            tb--;
        }
        sb[++tb]=i;
    }

    int ans=0,x=1,y=1;

    while(x<ta||y<tb){
        if(x==ta){
            ans+=(m-sb[y])*a[n];
            break;
        }
        if(y==tb){
            ans+=(n-sa[x])*b[m];
            break;
        }

        if(chk(sa[x+1]-sa[x],a[sa[x+1]]-a[sa[x]],sb[y+1]-sb[y],b[sb[y+1]]-b[sb[y]])){
            ans+=(sa[x+1]-sa[x])*b[sb[y]];
            x++;
        }
        else{
            ans+=(sb[y+1]-sb[y])*a[sa[x]];
            y++;
        }
    }

    cout<<ans<<"\n";

    return 0;
}
posted @ 2025-08-09 17:40  Lucyna_Kushinada  阅读(9)  评论(0)    收藏  举报