【题解】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;
}

浙公网安备 33010602011771号