【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;
}
posted @ 2021-08-24 09:37  あおいSakura  阅读(87)  评论(0编辑  收藏  举报