[USACO2021JAN] Minimum Cost Paths P
[USACO21JAN] Minimum Cost Paths P
题目描述
Farmer John 的牧草地可以看作是一个\(N×M\)(\(2≤N≤10^9, 2≤M≤2⋅10^5\))的正方形方格组成的二维方阵(想象一个巨大的棋盘)。对于 \(x∈[1,N],y∈[1,M]\),从上往下第 \(x\) 行、从左往右第 \(y\) 列的方格记为 \((x,y)\)。此外,对于每一个 \(y∈[1,M]\),第 \(y\) 列拥有一个代价 \(c_y\)(\(1≤c_y≤10^9\))。
Bessie 从方格 \((1,1)\) 出发。如果她现在位于方格 \((x,y)\),则她可以执行以下操作之一:
- 如果 \(y<M\),Bessie 可以以 \(x^2\) 的代价移动到下一列(\(y\) 增加一)。
- 如果 \(x<N\),Bessie 可以以 \(c_y\) 的代价移动到下一行(\(x\) 增加一)。
给定 \(Q\)(\(1≤Q≤2⋅10^5\))个独立的询问,每个询问给定 \((x_i,y_i)\)(\(x_i∈[1,N],y_i∈[1,M]\)),计算 Bessie 从 \((1,1)\) 移动到 \((x_i,y_i)\) 的最小总代价。
输入格式
输入的第一行包含 \(N\) 和 \(M\)。
第二行包含 \(M\) 个空格分隔的整数 \(c_1,c_2,…,c_M\)。
第三行包含 \(Q\)。
最后 \(Q\) 行每行包含两个空格分隔的整数 \(x_i\) 和 \(y_i\)。
输出格式
输出 \(Q\) 行,为每个询问的答案。
注意本题计算中所使用的整数大小可能需要使用 64 位整数型(例如,C/C++ 中的 long long)。
样例 #1
样例输入 #1
5 4
1 100 100 20
20
1 1
2 1
3 1
4 1
5 1
1 2
2 2
3 2
4 2
5 2
1 3
2 3
3 3
4 3
5 3
1 4
2 4
3 4
4 4
5 4
样例输出 #1
0
1
2
3
4
1
5
11
19
29
2
9
20
35
54
3
13
29
49
69
提示
样例 1 解释
输出以方阵形式表示如下:
1 2 3 4
*--*--*--*--*
1 | 0| 1| 2| 3|
*--*--*--*--*
2 | 1| 5| 9|13|
*--*--*--*--*
3 | 2|11|20|29|
*--*--*--*--*
4 | 3|19|35|49|
*--*--*--*--*
5 | 4|29|54|69|
*--*--*--*--*
测试点性质:
- 测试点 1-3 满足 \(N,M≤2000\)。
- 测试点 4-8 满足 \(c_2>c_3>⋯>c_M\)。
- 测试点 9-15 满足 \(N≤2⋅10^5\)。
- 测试点 16-20 没有额外限制。
供题:Benjamin Qi
考虑如果我在某一时刻从第 \(i\) 列直接到达了第 \(j\) 列,那么我最好是从哪一行走了过去。列出二次函数之后,得到最优的行在 \(f(i,j)=\lfloor\frac{c_j-c_i}{j-i}\rceil\)(\(\lfloor\rceil\)表示四舍五入)发现这东西很像凸包。
大胆猜想,最后所有转移点就是 \(f(i,j)\) 的下凸包。证明可以用保序回归。那么考虑离线并扫描线 \(i\),单调栈维护凸包,查询的时候在凸包上二分出来第一个超过 \(x\) 的点。在单调栈时预处理前缀和。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=2e5+5;
int n,m,c[N],st[N],tp,q,x[N];
LL ans[N],s[N];
vector<int>g[N];
int ask(int l,int r)
{
return min(max(1,(c[r]-c[l]+r-l)/(2*r-2*l)),n);
}
int find(int x)
{
int l=1,r=tp-1;
while(l<=r)
{
int md=l+r>>1;
if(ask(st[md],st[md+1])<=x)
l=md+1;
else
r=md-1;
}
return l;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
scanf("%d",c+i);
scanf("%d",&q);
for(int i=1,y;i<=q;i++)
scanf("%d%d",x+i,&y),g[y].push_back(i);
st[tp=1]=1;
for(int j:g[1])
ans[j]=1LL*c[1]*(x[j]-1);
for(int i=2,ls,nw;i<=m;i++)
{
while(tp>1&&1LL*(c[i]-c[st[tp]])*(st[tp]-st[tp-1])<=1LL*(c[st[tp]]-c[st[tp-1]])*(i-st[tp]))
--tp;
st[++tp]=i;
if(tp==2)
ls=1;
else
ls=ask(st[tp-2],st[tp-1]);
nw=ask(st[tp-1],st[tp]);
s[tp]=s[tp-1]+c[st[tp-1]]*1LL*(nw-ls)+(st[tp]-st[tp-1])*1LL*nw*nw;
for(int j:g[i])
{
int k=find(x[j]);
if(k==1)
ls=1;
else
ls=ask(st[k-1],st[k]);
ans[j]=s[k]+(x[j]-ls)*1LL*c[st[k]]+1LL*x[j]*x[j]*(i-st[k]);
}
}
for(int i=1;i<=q;i++)
printf("%lld\n",ans[i]);
}