TonyYin's Blog

Back

题目描述#

翻译的不是很好,所以我简化一下题面。

给定 nn 个矩阵的宽 ww 和高 hh,每次选一个矩阵集合,代价为集合中的 max{w}×max{h}\max{\{w\}}\times \max{\{h\}}

任意两次之间没有重复的矩阵,求取完所有矩阵的最小代价

定义#

矩阵的两个参数为 w,hw, h,分别对应下图中的宽和高。

函数 slope(i,j)\operatorname{slope}(i, j) 表示 (xi,yi)(x_i, y_i)(xj,yj)(x_j, y_j) 构成的直线的斜率x,yx, y 的含义在下面会说。

分析#

注意到对于两个矩阵 i,ji, j,如果 wiwjw_i\leq w_j 并且 hihjh_i\leq h_j,那么 iijj 一定可以放到一组,ii 对答案没有贡献,不用考虑,可以直接删去。

比如下图当中的 1122. 矩阵 22 能完全被 11 包含,所以 22 没有贡献,可以删去。

P2900-1

于是考虑先删除所有 ii 这样的矩阵。

具体做法就是:按照 hh 为第一关键字,ww 为第二关键字从大到小排序,之后双指针扫一遍即可。

//a已经降序排序
int t = 0;
for(int i = 1; i <= n; i++) {
	if(a[i].w > a[t].w) a[++t] = a[i];
}
cpp

如上图,剩下的只有 1,31, 3 号矩阵。

删完之后剩下的就是一个,hh 严格单调递减ww 严格单调递增的序列。考虑 DPDP 解决。

DP#

fif_i 为新序列中,取走前 ii 个矩阵的最小花费,ww 为宽(单调递增),hh 为高(单调递减),那么有转移方程:

fi=minj=0i{fj+wihj+1}f_i=\min_{j=0}^{i}{\{f_{j}+w_ih_{j+1}\}}

相当于,把 j+1j+1ii 这一段合为一组取走。由于单调性,所以这一组的 max{w}=wi\max{\{w\}}=w_imax{h}=hj+1\max{\{h\}}=h_{j+1}.

复杂度 O(n2)\mathcal{O}(n^2)。这显然可以斜率优化。

斜率优化#

我们目前优化的目的,是尽快地找到 fif_i 到底是由哪个 jj 转移过来的,也就是尽快找到最小化 fif_i 的继承状态。

为了表示方便,设 jj 为能使 fif_i 最小化的继承状态,所以有:

fi=fj+wihj+1f_i=f_{j}+w_ih_{j+1}

考虑一次函数斜截式 y=kx+by=kx+bb=ykxb=y-kx,将状态转移方程变换为这个形式:

fj=wihj+1+fif_{j}=-w_ih_{j+1}+f_i

其中变量 x,yx, yjj 有关,b,kb, kjj 无关。且要求 xxjj 单调递增,bb 中包含 fif_i(斜率优化基本操作)。

所以可以构造出直线方程为:

{y=fjk=wix=hj+1b=fi\left\{ \begin{array}{lr} y = f_j\\ k = w_i\\ x = -h_{j+1}\\ b = f_i \end{array} \right.

我们把 (x,y)(x, y) 看成平面直角坐标系上的点。那么现在 (xj,yj)(x_j, y_j) 的意义就是:直线 y=kx+by=kx+b 上的一个点

又因为我们的目的是最小化 fif_i,在上面的表示当中,fif_i 只与直线的截距 bb 有关。所以显然地,问题转化为:如何选取 jj ,才能使得过点 (xj,yj)(x_j, y_j) 的直线的截距 bb 最小。

20190701195640154

显然,对于一个斜率 kk 来说,最优解 jj 就是从左往右枚举到的第一个 slope(j,j+1)>k\operatorname{slope}(j, j+1)>k 的点。(其中 slope\operatorname{slope} 的定义在上面有说过)

再考虑单调性: xxjj 单调递增,kkii 单调递增,要使截距 b=fib=f_i 最小。

显然,所有的最优决策点 jj 都在下凸包上,也就是上图中的折线。

所以单调队列维护下凸包即可:

设单调队列(递增)为 qq,队首为 headhead,队尾为 tailtail,当前要转移 ii 号点,那么具体步骤:

  1. slope(qhead,qhead+1)wi=ki\operatorname{slope}(q_{head}, q_{head+1})\leq w_i=k_i 时,headhead++,(保证最优解)
  2. 此时的 qheadq_{head} 就是最优转移点,根据它和转移方程计算出 fif_i
  3. slope(qtail1,qtail)slope(qtail,i)\operatorname{slope}(q_{tail-1}, q_{tail})\geq\operatorname{slope}(q_{tail}, i) 时,tailtail—,(维护下凸包
  4. 在队尾插入 ii.

代码#

#include <bits/stdc++.h>
#define X(i) (-a[i+1].h)
#define Y(i) (f[i])
#define int long long
using namespace std;
const int MAXN = 5e4 + 10;
int n;
struct Node{
	int w, h;
} a[MAXN];
int f[MAXN];
bool cmp(Node i, Node j) {
	return (i.h > j.h) || (i.h == j.h && i.w > j.w);
}
double slope(int i, int j) {
	return (Y(j) - Y(i)) / (double)(X(j) - X(i));
}
int head, tail, q[MAXN];
signed main() {
	scanf("%lld", &n);
	for(int i = 1; i <= n; i++) {
		scanf("%lld%lld", &a[i].w, &a[i].h);
	}
	sort(a + 1, a + n + 1, cmp);
	int t = 0;
	for(int i = 1; i <= n; i++) {
		if(a[i].w > a[t].w) a[++t] = a[i];
	}
	n = t;
	head = tail = 1;
	for(int i = 1; i <= n; i++) {
		while(head < tail && slope(q[head], q[head + 1]) <= a[i].w) {
			head++;
		}
		int j = q[head]; f[i] = f[j] + a[i].w * a[j + 1].h;
		while(head < tail && slope(q[tail - 1], q[tail]) >= slope(q[tail], i)) {
			tail--;
		}
		q[++tail] = i;
	}
	cout << f[n] << endl;
	return 0;
}
c
【洛谷-P2900】Land Acquisition
https://www.tonyyin.top/blog/oi-solution/p2900
Author TonyYin
Published at February 25, 2021
Comment seems to stuck. Try to refresh?✨