【矩阵分割】题解
题目描述
平面上有一个大矩形,其左下角坐标(0,0),右上角坐标(R,R)。大矩形内部包含一些小矩形,小矩形都平行于坐标轴且互不重叠。所有矩形的顶点都是整点。要求画一根平行于y轴的直线x=k(k是整数) ,使得这些小矩形落在直线左边的面积必须大于等于落在右边的面积,且两边面积之差最小。并且,要使得大矩形在直线左边的的面积尽可能大。注意:若直线穿过一个小矩形,将会把它切成两个部分,分属左右两侧。
输入格式
第一行是整数R,表示大矩形的右上角坐标是(R,R) (1 <= R <= 1,000,000)。
接下来的一行是整数N,表示一共有N个小矩形(0 < N <= 10000)。
再接下来有N 行。每行有4个整数,L,T, W 和 H, 表示有一个小矩形的左上角坐标是(L,T),宽度是W,高度是H (0<=L,T <= R, 0 < W,H <= R). 小矩形不会有位于大矩形之外的部分。
输出格式
输出整数n,表示答案应该是直线 x=n。 如果必要的话,x=R也可以是答案。
思路:
首先二分答案,即直线
x
x
x 的位置。做完题后发现输入的
T
T
T 没什么用。
先明确输入:在平面直角坐标系中,对于每个点的坐标 ( x , y ) (x,y) (x,y), x x x 表示列, y y y 表示行,这与 C++ 中的数组恰好相反。但我们不需要知道这么多,每个矩形的高都为 H [ i ] H[i] H[i],宽都为 W [ i ] W[i] W[i],求它的面积就只用 S = H [ i ] × W [ i ] S=H[i] \times W[i] S=H[i]×W[i] 即可。
check
函数的写法:对于每个矩形,有三种情况:
- 全部在 x x x 的左侧,即最右边 L [ i ] + W [ i ] < x L[i]+W[i]<x L[i]+W[i]<x
if(L[i] + W[i] < x) sum1 += H[i] * W[i];
- 恰好被 x x x 分为两部分,即最左边 L [ i ] < = x L[i]<=x L[i]<=x,最右边 L [ i ] + W [ i ] > = x L[i]+W[i]>=x L[i]+W[i]>=x
if(L[i] <= x and L[i] + W[i] >= x)
sum1 += H[i] * (x - L[i] + 1), // 宽度为 x 分别到左右两边的距离
sum2 += H[i] * (L[i] + W[i] - x + 1); // 上下两处可加一也可不加,不影响结果,自己思考问什么
- 全部在 x x x 的右侧,即最左边 L [ i ] > x L[i]>x L[i]>x
if(L[i] > x) sum2 += H[i] * W[i];
二分的写法还是没什么好说的,套就可以了。
坑点:
- 由于
1
<
=
R
<
=
1
e
6
1 <= R <= 1e6
1<=R<=1e6,算面积时
R
2
R^2
R2 是肯定会爆
int
的,所以算面积的check
函数要开long long
,既然局部开了long long
,那么最好还是把所有变量都开long long
,以免莫名奇妙60 分
。 - 并且,要使得大矩形在直线左边的的面积尽可能大。 所以,在面积差不变的情况下,答案要尽量向右。而向右的过程中,又要满足答案不超过边界 R R R。
Code:
#include <cstdio>
#include <algorithm>
typedef long long LL; // 将 long long 改名为 LL
using namespace std;
const int Maxn = 1e4 + 5;
LL R, n, l = 1e6, r, L[Maxn], T[Maxn], W[Maxn], H[Maxn];
LL check(LL x) { // 解释见上文
LL sum1 = 0, sum2 = 0;
for(int i = 1;i <= n; ++i) {
if(L[i] + W[i] < x) sum1 += H[i] * W[i];
else if(L[i] > x) sum2 += H[i] * W[i];
else sum1 += H[i] * (x - L[i] + 1), sum2 += H[i] * (L[i] + W[i] - x + 1);
}
return sum1 - sum2;
}
int main() {
scanf("%lld %lld", &R, &n);
for(int i = 1;i <= n; ++i) {
scanf("%lld %lld %lld %lld", &L[i], &T[i], &W[i], &H[i]);
l = min(l, L[i]), r = max(r, L[i] + W[i]);
}
// 二分
while(l < r) { // 还剩一个数时退出
LL mid = l + r >> 1; // 当还剩两个点时,mid定为l,下一次l=mid+1=r,肯定能跳出来
if(check(mid) < 0) l = mid + 1; // 左边面积小了,将x右移。这样说明答案没有可能是mid,所以要加一
else r = mid; // 在左面积大于右面积的情况下找最小
}
while(check(r + 1) == check(r) and r < R) r ++; // 往右不影响面积差,且不超过边界
printf("%lld", r);
return 0;
}
听说还有种前缀和的做法,我暂时还没想出来,欢迎大家讨论!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现