【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\) 会怎么样,就会要有一个位置从 \(m-1\) 向下走,然后剩下全部往右走。
那我们就考虑一开始全部往右走,那贡献就是 \(x-1\)。
然后考虑在一个地方 \((x,y)\) 往下走新多的贡献。(后面的还是全部往右)
首先是 \(c_y\),然后剩下的往右走是 \((m-y)*(x+1)^2\),然后之前剩下的往右走是 \((m-y)*x^2\),相减就是 \((m-y)*(2x+1)\),所以新多的就是 \(c_y+(m-y)*(2x+1)\)。
然后拆开移一下:\(c_y-y-2xy+2xm+m\)。
那右边的 \(2xm+m\) 都是迟早都要,可以单独拿出来,最后再算贡献。
那有关的就是 \(c_y-y-2xy\),然后发现你可以看做是 \((-2y)x+(c_y-y)\)。
(考虑算最小值)
那就是一条直线,单调下降。
然后你要每次选的 \(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;
}