P7294 题解

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

题意简述

给出一个 $n\times m(1\le n\le 10^9,1\le m\le 2\times10^5)$ 的棋盘。设 $(i,j)$ 为第 $i$ 行第 $j$ 列的格子。从 $(1,1)$ 开始走,从 $(i,j)(1\le i<n,1\le j\le m)$ 走到 $(i+1,j)$ 代价为 $c_j$;从 $(i,j)(1\le i\le n,1\le j<m)$ 走到 $(i,j+1)$ 代价为 $i^2$。

再来 $q(1\le q\le 2\times 10^5)$ 次询问,每次给出坐标 $(x,y)$,求从 $(1,1)$ 走到 $(x,y)$ 的最小代价。

题目分析

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

  • $n,m\le 2000$

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

  • $c_2>c_3>\cdots>c_m$

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

那么就好处理了。我们只需要依据 $j=1$ 时是从哪一行开始横着走的就能算出最小代价。如果是从第 $i$ 行开始,则代价为 $(i-1)c_1+(y-1)i^2+(x-i)c_y$。为了求最优的 $i$,我们把它表示成以 $i$ 为主元的形式:$(y-1)i^2+(c_1-c_y)i+(xc_y-c_1)$。当 $y>1$ 时这是一个开口向上的二次函数,所以我们可以根据它的对称轴 $\displaystyle i=\frac{c_y-c_1}{2(y-1)}$ 找到最优的 $i$(将对称轴四舍五入之后对 $1$ 取 $\max$ 再对 $x$ 取 $\min$ 即可);否则 $y=1$ 时,由上文的 DP 可知代价为 $(x-1)c_1$。时间复杂度 $O(m+q)$。

  • $n\le 2\times 10^5$、$n\le 10^9$

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

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

时间复杂度 $O(q\log n+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 @ 2023-12-18 19:44  Hadtsti  阅读(27)  评论(0编辑  收藏  举报  来源