【luogu P7294】Minimum Cost Paths P(二分)(单调栈)(斜率)

Minimum Cost Paths P

题目链接:luogu P7294

题目大意

给你一个 n*m 的矩阵,一开始你在 (1,1)。
如果你在 (x,y),你可以花费 x^2 的费用走到 (x,y+1),也可以花费 cy 的费用走到 (x+1,y)。
然后多组询问,每次问你要走到 (x,y) 的最小费用。

思路

由于 n 很大,m 比较小,我们考虑离线,然后把询问按 m 从小到大排。
那我们考虑 m 多了 1 会怎么样,就会要有一个位置从 m1 向下走,然后剩下全部往右走。
那我们就考虑一开始全部往右走,那贡献就是 x1

然后考虑在一个地方 (x,y) 往下走新多的贡献。(后面的还是全部往右)
首先是 cy,然后剩下的往右走是 (my)(x+1)2,然后之前剩下的往右走是 (my)x2,相减就是 (my)(2x+1),所以新多的就是 cy+(my)(2x+1)

然后拆开移一下:cyy2xy+2xm+m
那右边的 2xm+m 都是迟早都要,可以单独拿出来,最后再算贡献。
那有关的就是 cyy2xy,然后发现你可以看做是 (2y)x+(cyy)
(考虑算最小值)

那就是一条直线,单调下降。
然后你要每次选的 y 递增,那就是要维护一个上凸壳。(用单调栈维护)
至于里面的直线,你考虑维护它在 x 为那个区间范围的时候最优,然后你询问你就二分出这个直线,前面的提前前缀和统计好,然后这条之间再算一次,就可以了。

(记得加上之前省略的值)

代码

#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define ll long long using namespace std; struct ask { ll x, y, num; ll ans; }qq[200001]; ll n, m, q, sta[200001], top; ll l[200001], r[200001]; ll c[200001], sum[200001]; ll k[200001], b[200001]; bool cmp1(ask x, ask y) { return x.y < y.y; } bool cmp2(ask x, ask y) { return x.num < y.num; } ll met(ll x, ll y) {//算出两条直线的交点(向上取整) return (b[y] - b[x] + (k[x] - k[y] - 1)) / (k[x] - k[y]); } bool worse(ll i, ll j) { if (met(i, j) <= l[i]) return 1; return 0; } ll get_line(ll x, ll l, ll r) {//算一整条的贡献 return (l + r) * (r - l + 1) / 2 * k[x] + (r - l + 1) * b[x]; } int main() { scanf("%lld %lld", &n, &m); for (ll i = 1; i <= m; i++) { scanf("%lld", &c[i]); b[i] = c[i] - i; k[i] = -2 * i; } scanf("%lld", &q); for (ll i = 1; i <= q; i++) { scanf("%d %d", &qq[i].x, &qq[i].y); qq[i].num = i; } sort(qq + 1, qq + q + 1, cmp1); ll now = 1; for (ll i = 1; i <= m; i++) { r[i] = n; while (top && worse(sta[top], i)) top--; if (top) l[i] = max(1ll, met(sta[top], i)); else l[i] = 1; if (l[i] <= r[i]) { r[sta[top]] = l[i] - 1; sta[++top] = i; sum[top - 1] = ((top - 1) ? sum[top - 2] : 0) + get_line(sta[top - 1], l[sta[top - 1]], r[sta[top - 1]]); sum[top] = sum[top - 1] + get_line(sta[top], l[sta[top]], r[sta[top]]); } while (now <= q && qq[now].y == i) { ll ans = -1, lll = 0, rr = top; while (lll <= rr) { ll mid = (lll + rr) >> 1; if (r[sta[mid]] >= qq[now].x - 1) rr = mid - 1; else lll = mid + 1, ans = mid; } qq[now].ans = sum[ans] + get_line(sta[ans + 1], l[sta[ans + 1]], qq[now].x - 1); qq[now].ans += (qq[now].x - 1) * (qq[now].x + 1) * qq[now].y; qq[now].ans += qq[now].y - 1;//后面两个是把之前没有算的贡献全部加上 now++; } } sort(qq + 1, qq + q + 1, cmp2); for (ll i = 1; i <= q; i++) printf("%lld\n", qq[i].ans); return 0; }

__EOF__

本文作者あおいSakura
本文链接https://www.cnblogs.com/Sakura-TJH/p/luogu_P7294.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   あおいSakura  阅读(92)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示