P7294 题解

今天模拟赛 T3 出了这道题,结果剩下时间太短正解没想完

题意简述

给出一个 n×m(1n109,1m2×105) 的棋盘。设 (i,j) 为第 i 行第 j 列的格子。从 (1,1) 开始走,从 (i,j)(1i<n,1jm) 走到 (i+1,j) 代价为 cj;从 (i,j)(1in,1j<m) 走到 (i,j+1) 代价为 i2

再来 q(1q2×105) 次询问,每次给出坐标 (x,y),求从 (1,1) 走到 (x,y) 的最小代价。

题目分析

由于是模拟赛题,我就按赛场上的思路写,因此从部分分开始。

  • n,m2000

简单的 DP,不用多说。预处理 O(nm),询问 O(1)

  • c2>c3>>cm

考虑一下这个性质的作用。其实就是说,在 j2 时应该先横着走到 j=y,再竖着走到 i=x。证明也很简单:如果在 j2 时,存在有一步是从 (i,j) 先走到 (i+1,j) 再走到 (i+1,j+1),则此时的代价是 cj+(i+1)2>cj+1+i2,即先走到 (i,j+1) 再走到 (i+1,j+1) 是更优的。所以通过调整,我们得到总的路径也是先横着走再竖着走。

那么就好处理了。我们只需要依据 j=1 时是从哪一行开始横着走的就能算出最小代价。如果是从第 i 行开始,则代价为 (i1)c1+(y1)i2+(xi)cy。为了求最优的 i,我们把它表示成以 i 为主元的形式:(y1)i2+(c1cy)i+(xcyc1)。当 y>1 时这是一个开口向上的二次函数,所以我们可以根据它的对称轴 i=cyc12(y1) 找到最优的 i(将对称轴四舍五入之后对 1max 再对 xmin 即可);否则 y=1 时,由上文的 DP 可知代价为 (x1)c1。时间复杂度 O(m+q)

  • n2×105n109

其实受到上一部分的启发剩下的两个范围都可以做了。

注意到上一部分中的最优的 i 取值实际上只和 j=1j=y 有关(当然忽略范围问题导致的取 min/max 问题),所以考虑把询问按 y 分类离线处理。我们由刚刚的分析可得,每次路径出现“横走——竖走”的情况时(即 (i,j)(i,j+Δj)(i+Δi,j+Δj) 时),应该尽可能使得开始向右走的 i 接近 cj+Δjcj2Δj。为了 i 最优且是递增的,我们可以采用单调栈维护。具体地,假设我们竖着走的列决策分别为 j1,j2,,jk,我们就需要保证 1lk2,cjl+1cjl2(jl+1jl)<cjl+2cjl+12(jl+2jl+1)。这只需要在每次插入需要查询的 j=y 之前,不断检查栈顶和次栈顶决策是否满足最优的 i 小于当前决策和栈顶决策算得的最优的 i 即可,不满足就弹掉,最后 j=y 入栈。至于计算每个具体 (x,y) 的答案,只需要在维护栈的同时维护决策算得代价的前缀和,每次二分找到 x 在哪两个决策算得的最优 i 之间并记录对应答案即可。

时间复杂度 O(qlogn+m),可以通过本题。

代码实现

#include<bits/stdc++.h>
using namespace std;
int n,m,q,a[200010],c[200010],stk[200010],top,ID;//stk[]:栈,top:栈顶,a[]:决策计算出的最优 i 位置,方便二分
long long sum[200010],ans[200010];//sum[]:代价前缀和,ans[]:询问答案
vector<pair<int,int> >Q[200010];
int P(int i,int j)
{
return min(n,max(int(0.5*(double)(c[j]-c[i])/(j-i)+0.5),1));
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
scanf("%d",&c[i]);
scanf("%d",&q);
for(int x,y,i=1;i<=q;i++)
scanf("%d%d",&x,&y),Q[y].push_back({x,i});//把询问按 y 分类,离线下来
for(auto i:Q[1])
ans[i.second]=1ll*c[1]*(i.first-1);//特判 y=1
top=stk[1]=a[1]=1;//最初决策是 y=1
for(int i=2;i<=m;i++)
{
while(top>1&&a[top]>=P(stk[top],i))
top--;//弹掉不合法的决策
int x=P(stk[top],i);
sum[top+1]=sum[top]+1ll*(x-a[top])*c[stk[top]]+1ll*(i-stk[top])*x*x;//维护代价的前缀和
stk[++top]=i,a[top]=x;
for(auto t:Q[i])
{
ID=upper_bound(a+1,a+top+1,t.first)-a-1;//二分找到对应决策位置
ans[t.second]=sum[ID]+1ll*(t.first-a[ID])*c[stk[ID]]+1ll*(i-stk[ID])*t.first*t.first;//保存答案
}
}
for(int i=1;i<=q;i++)
printf("%lld\n",ans[i]);
return 0;
}
posted @   Hadtsti  阅读(39)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
点击右上角即可分享
微信分享提示