【矩阵分割】题解

Link

题目描述

平面上有一个大矩形,其左下角坐标(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 函数的写法:对于每个矩形,有三种情况:

  1. 全部在 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];
  1. 恰好被 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); // 上下两处可加一也可不加,不影响结果,自己思考问什么
  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;
}

听说还有种前缀和的做法,我暂时还没想出来,欢迎大家讨论!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值