【凸壳】luogu_P7249 Minimum Cost Paths P

题意

在一个 \(N \times M\) 的方格图上。
\((x,y)\),有两种移动方式:

  • 可以以 \(x^2\) 的代价向右移动。
  • 可以以 \(c_y\) 的代价向下移动。

对于\(Q\)个询问,求出从 \((1,1)\)\((x,y)\) 的最小代价。
\(100 \%, 2≤N≤10^9,2≤Q,M≤2 \times 10^5\)

思路

设想答案是一条 \((1,1)\)\((1,y)\) 的直线选择若干个转折点向下折到 \((x,y)\)

假设当前点为 \((x,y)\) ,要走到 \((n,m)\) ,那么向下折一下的代价为 \(c_y+(m-y)(2x+1)\) ,后面这一段是根据完全平方公式算出向右走要加上的代价。

拆开这个式子,得 \(c_y+2xm+m-2xy-y\),将关于 \(x\) 的常数项扔掉,得到了一个关于 \(x\) 的函数 \(c_y-2xy-y\)
对于 \(x\) ,选择一个 \(y\) 向下折使得代价最小,即可最小化答案。

然而我们只能向右走不能向左走,这就要求选出的y是递增的。观察 \(c_y-2xy-y\) ,发现\(-2y\)是随着 \(y\) 递增而递减的。
而最小化这个式子,需要维护一个下凸壳,这样子对于递增的\(x\)\(-2y\)是递减的,保证了\(y\)是递增的。

那么直接做即可。

代码

#include <cmath>
#include <cstdio>
#include <algorithm>
#define int long long

struct node {
	int x, y, id;
} que[200001];
int n, m, q, top;
int k[200001], b[200001], s[200001], sum[200001], l[200001], r[200001], ans[200001];

int cmp(node x, node y) {
	return x.y < y.y;
}

int cross(int i, int j) {
	return ceil((double)(b[j] - b[i]) / (k[i] - k[j]));
}

int calc(int i, int l, int r) {
	return (k[i] * (l + r) + 2 * b[i]) / 2 * (r - l + 1);//woshishabi:!!!
}

signed main() {
	scanf("%lld %lld", &n, &m);
	for (int i = 1; i <= m; i++)
		scanf("%lld", &b[i]), b[i] -= i, k[i] = -2 * i;
	scanf("%lld", &q);
	for (int i = 1; i <= q; i++)
		scanf("%lld %lld", &que[i].x, &que[i].y), que[i].id = i;
	std::sort(que + 1, que + q + 1, cmp);
	for (int i = 1, pt = 1; i <= m; i++) {
		while (top && cross(i, s[top]) <= l[s[top]])
			top--;//维护单调性
		l[i] = top ? cross(i, s[top]) : 1;
		r[i] = n;
		if (l[i] <= r[i]) {//更新一系列值
			r[s[top]] = l[i] - 1;
			if (top)
				sum[top] = sum[top - 1] + calc(s[top], l[s[top]], r[s[top]]);
			s[++top] = i;
			sum[top] = sum[top - 1] + calc(i, l[i], r[i]);
		}
		while (que[pt].y == i && pt <= q) {//求答案
			int qx = que[pt].x - 1, ll = 1, rr = top;
			while (ll < rr) {
				int mid = ll + rr >> 1;
				if (qx <= r[s[mid]])
					rr = mid;
				else
					ll = mid + 1;
			}
			ans[que[pt].id] = sum[ll - 1] + calc(s[ll], l[s[ll]], qx) + qx * i * (2 + qx) + i - 1;//横着走的代价最后算即可
			pt++;
		}
	}
	for (int i = 1; i <= q; i++)
		printf("%lld\n", ans[i]);
}

巨大坑点

又犯下了先×后➗的滔天大罪!

posted @ 2021-08-19 10:29  nymph181  阅读(45)  评论(0)    收藏  举报