洛谷 P11104 [ROI 2023] 监控 (Day 1) 题解

题目传送门

题目描述的很清晰,图都配了,就不赘述题面了。

思路分析

问题一:求最小紧凑性

首先可以很容易发现,紧凑性便是以横坐标最大和最小的两个摄像头画面的横轴距离为长、以纵坐标最小和最大的两个摄像头画面的纵轴距离为宽的矩形面积,所以我们只需要让两者尽可能小就行了。

显然,左右的移动和上下移动的这两类操作对于最终答案的贡献是分开的,两者不会互相干扰。以题目第二组样例为例,如图,蓝色部分即为摄像头画面:

对于此时的图像画面而言,紧凑性为 \(4\times3=12\)

执行右移操作,则画面变更为:

紧凑性变为 \(2\times3=6\)

对于以上左移操作,出现变化的只有横坐标最大和最小的两个摄像头画面的横轴距离,而纵向并没有受到左移操作的影响。同理,上下移动操作也不会影响到横向,所以我们可以放心大胆地把横向和纵向分开讨论。

仍以题目第二组样例为例:

先来看横向的。既然我们要求的是横坐标最大和最小的两个摄像头画面的横轴距离,那我们不妨先给所有横坐标进行排序,我们便得到了这么一个序列 \(a\)

对应到图中,则变成了这样:

(tips:重复的元素其实无所谓,实际代码中去不去重不影响判断)

设最终答案的横长为 \(X\) ,不难发现,当且仅当有元素从一个端点移动到另一个端点时 \(X\) 才会发生变化,其余步骤移动均不会改变 \(X\) 的值。

而从一个端点移动到另一个端点这一步骤,我们既能通过左移来实现,也能通过右移来实现。具体地,如下图,如果我想把左边的画面移动到最右边,既可以通过左移一步来达成,也可以通过右移三步来达成,而其他的画面的位置会跟随其一并发生变化,移动的方式最终不会影响到 \(X\) 的值。

所以先抛开右移操作,仅考虑左移:发生左右端点间的移动,便是把 \(a\) 中的值最小的点的值变化为 \(h\) ,其余的点的值则减去最小值。由于我们已经给 \(a\) 排好序了,所以其中的最小值便是 \(a_1\) 了,经过左移到左端点后再进行一次左移的操作后,序列 \(a\) 就会变成 $ h,a_2 - a_1 ,\cdots, a_k - a_1$ 。我们再对其排一次序,得到 $ a_2 - a_1 ,\cdots, a_k - a_1,h$

此时,我们再代入求 \(X\) 的公式,便会得到 \(X=h-(a_2-a_1)+1\)。而如果把现在的序列 \(a\) 再进行一次将最小值移动到右端点的操作,我们又能得到一个新的 \(X\) 。可以发现,一共存在 \(K\) 种可能的 \(X\) ,推到以下可得:\(X_i=h-(a_i-a_{i-1})+1\ (\ 1 < i \leq k)\) 其中,\(X_i\) 表示以原数组 \(a\) 的第 \(i\) 个数作为最小值\(X\) 的值。

带入到样例中,我们能得到如下图所示的序列 \(X\)

欸?那 \(X_1\) 怎么办呢?我们只需要在最开始给 \(a\) 排好序的时候直接 \(a_k-a_1+1\) 就好啦,因为 \(a_1\) 本来就是这么多节点横坐标的最小值嘛:

于是这样,我们就得到了所有可能的 \(X\)

相信这时候就有聪明的同学会想到,题目要求的明明是最小值的紧凑性,那我直接维护最小的 \(X\) 不就行了吗?费这个空间把所有 \(X\) 存下来干嘛?

没错,我们在枚举所有的 \(X\) 时仅需要维护最小的那个就可以了,没必要保留其他那些没什么用处的数据:

纵向上同理,设最终答案的纵长为 \(Y\) ,同相同的方法挨个枚举可能的 \(Y\) 并记录最小值就行。这样我们就解决了第一个问题。

问题二:达成最小紧凑性时的步数

别忘了题目还有一个问题呢!

前面我们在求最小紧凑性的时候,已经锁定了最小的 \(X\)\(Y\) 的值,并且在枚举两者的时候找到了它们对应的点的坐标位置,那我们不妨在枚举的同时再额外考虑一下如何到达这个问题。

依旧以样例二、横向操作为例:

前文提到,“ 从一个端点移动到另一个端点这一步骤,我们既能通过左移来实现,也能通过右移来实现 ”,具体地,想让数组 \(a\) 中的最小值成为最大值,可以通过将其左移到左界并再次左移,或者是将其一直右移直至右界:

(素材复用)

但仅仅只有这两种方法了吗?并不一定。

仔细思考可以发现,对于每个 \(X_i\) ,枚举时我们的目的仅仅只是让最左端点移动成为数组 \(a\) 中的最大值,而不一定是让它成为右界 \(h\)

样例二并不能很好地体现这一点,我们换一张图:

显然,我们在考虑最左边的 \(a_1\) 右移成为最大值时,其实只需要右移两次、把 \(a_2\) 右移超过右界来到左界就可以了,并不需要把 \(a_1\) 移动到最右端:

所以 让最左端点移动成为数组 \(a\) 中的最大值 这个问题也可以转变为 让最左端点右侧的第一个端点移动成为数组 \(a\) 中的最小值

面对转换后的问题,最简单的方式不就是把 \(a_2\) 这家伙给弄到左端点的位置上去吗?而这又有了左移和右移两种方法。

于是,我们可以得到,对于每个 \(X_i\) ,最简单地达成它一共有四种方式,即四种移动步骤:

1.将 \(a_{i-1}\) 左移至左端点的位置再左移一次,步数: \(a_{i-1}\)

2.将 \(a_{i-1}\) 右移至右端点的位置,步数: \(h-a_{i-1}\)

3.将 \(a_i\) 左移至左端点的位置,步数: \(a_i-1\)

4.将 \(a_i\) 右移至右端点的位置再右移一次,步数: \(h-a_i+1\)

由于题目需要的是最小步数,所以我们就取这四个值里头最小的那个作为 \(X_i\) 对应的步数就可以啦!

纵向也是一个道理,最后输出答案的时候输出最小的 \(X\) 和最小的 \(Y\) 相乘的结果和他们对应的最小步数相加就完工啦!

恭喜你切了这道题!

最后,进点食:

十年OI________,不开________见祖宗!

Code

#include<bits/stdc++.h>
using namespace std;
const int MAXK=1e5+5;
#define int long long
int h,w,k,x[MAXK],y[MAXK];      //x[]对应横坐标,即文中的a,y[]对应纵坐标
int min_x,min_y,min_stepx=0,min_stepy=0;    //min_x、min_y分别是最小的X和最小的Y;min_stepx和min_stepy则分别对应X和Y最小时的步数
signed main()
{
	cin>>h>>w>>k;
	for(int i=1;i<=k;i++)
		cin>>x[i]>>y[i];
	if(k==1)                //这里加了个特判,因为就一个点的时候答案是一定的,再跑一遍程序浪费时间,其实加不加无所谓
	{
		cout<<"1 0";
		return 0;
	}
	sort(x+1,x+1+k);
	sort(y+1,y+1+k);
	min_x=x[k]-x[1]+1;
	min_y=y[k]-y[1]+1;
	for(int i=1;i<=k;i++)
	{
		if(min_x==(h-(x[i]-x[i-1])+1))
			min_stepx=min(min_stepx,min(x[i-1],min(h-x[i-1],min((x[i]-1),(h-x[i]+1)))));
		else
		{
			min_x=min(min_x,(h-(x[i]-x[i-1])+1));
			if(min_x==(h-(x[i]-x[i-1])+1))
				min_stepx=min(x[i-1],min(h-x[i-1],min((x[i]-1),(h-x[i]+1))));
		}
		if(min_y==(w-(y[i]-y[i-1])+1))
			min_stepy=min(min_stepy,min(y[i-1],min(w-y[i-1],min((y[i]-1),(w-y[i]+1)))));
		else
		{
			min_y=min(min_y,(w-(y[i]-y[i-1])+1));
			if(min_y==(w-(y[i]-y[i-1])+1))
				min_stepy=min(y[i-1],min(w-y[i-1],min((y[i]-1),(w-y[i]+1))));
		}
	}
	cout<<(min_x*min_y)<<" "<<(min_stepx+min_stepy);
	return 0;
}
posted @ 2025-10-29 18:44  Kaos·Abraham  阅读(53)  评论(0)    收藏  举报