题目
http://noi.openjudge.cn/ch0111/03/
描述
平面上有一个大矩形,其左下角坐标(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也可以是答案。
样例输入
1000
2
1 1 2 1
5 1 2 1
样例输出
5
思路
- 二分查找,找到满足“左侧面积>=右侧面积”的可能值
- 调整可能值
对于非特殊情况的:最终值 mid=min
对于左侧面积 = = 右侧面积 的:找到满足“左侧面积 = =右侧面积”的最大整数值
对于右侧面积为0的:最终值 = 大矩形的边界 r
几种特殊情况
- 情况1
由于“只能取整数”且“左侧要比右侧大”,所以划分直线应该为x=2
然而又考虑到“大矩形左侧尽可能大”,所以划分直线应该为x=r(r为大矩形边界)
- 情况2
两侧面积相等,中间有一段空间区域,如果只满足“左侧要比右侧大或相等”,则直线可以在这段空间内移动
但同时受条件“大矩形左侧尽可能大”就有了一个边界点
注意点
- 使用 long long,而不是int(总面积很有可能超出2^32)
- 两个限制条件以及只能取整数点带来的一些问题,比如特殊情况x=r
几个问题
二分查找的边界收敛问题
可以使用 mid=min
参考 https://blog.csdn.net/OCEANtroye/article/details/82811432
使用函数,而不是把面积计算放入代码里:
- 实参、形参传递
注意“传地址时”函数定义内使用,需要为 *left 格式,而不是直接left
注意实参形参传递时,函数 定义 和 声明 内均需要标注 &或 *,如果有二者有其一为标注 ,则会在编译时提示“严重错误:无法解析的外部符号”
方法一 :传地址
//定义
void area(long long * left1,long long * right1, Rec re[],long long mid,int n)
{
int i;
*right1 = 0, *left1 = 0;
for (i = 0; i < n; i++)
{
if (re[i].l >= mid)
{
*right1 += re[i].w * re[i].h;
}
else
{
if (re[i].l + re[i].w <= mid)
{
*left1 += re[i].w * re[i].h;
}
else
{
*left1 += (mid - re[i].l) * re[i].h;
*right1 += (re[i].l + re[i].w - mid) * re[i].h;
}
}
}
}
//使用
area(&left1,&right1, re, mid, n);
**方法二:**传引用
//定义
void area(long long & left1,long long & right1, Rec re[],long long mid,int n)
{
int i;
right1 = 0, left1 = 0;
for (i = 0; i < n; i++)
{
if (re[i].l >= mid)
{
right1 += re[i].w * re[i].h;
}
else
{
if (re[i].l + re[i].w <= mid)
{
left1 += re[i].w * re[i].h;
}
else
{
left1 += (mid - re[i].l) * re[i].h;
right1 += (re[i].l + re[i].w - mid) * re[i].h;
}
}
}
}
//使用
area(left1,right1, re, mid, n);
- 全局变量
问题1:
left、right是不是不可以作为全局变量的名字?使用后报错(定义不明确)?
解决:
可能与using namespace std有关
https://www.cnblogs.com/dj0325/p/8491649.html(定义不明确问题)
问题2:
“全局变量使用无效”问题,即将左右面积left、right定义为全局变量,但面积计算函数中left、right的计算传递不到 main函数中
后来发现我对全局变量的理解有问题,我把这两个定义为全局变量了,就不必再用形参传递了
参考https://blog.csdn.net/xiaohu1996/article/details/83957446
例如:
以下面积计算函数是正确的,但如果是
void area(long long right1,long long left1, Rec re[],long long mid,int n)
就是错误的
//错误
void area(long long right1,long long left1, Rec re[],long long mid,int n)
//正确
void area( Rec re[],long long mid,int n)
//正确
void area( Rec re[],long long mid,int n)
{
int i;
right1 = 0, left1 = 0;
for (i = 0; i < n; i++)
{
if (re[i].l >= mid)
{
right1 += re[i].w * re[i].h;
}
else
{
if (re[i].l + re[i].w <= mid)
{
left1 += re[i].w * re[i].h;
}
else
{
left1 += (mid - re[i].l) * re[i].h;
right1 += (re[i].l + re[i].w - mid) * re[i].h;
}
}
}
}
测试数据
1000 1
0 1 4 1
答案:2
1000 1
0 1 5 1
答案:3
1000 2
0 1 4 1
8 1 4 1
答案:8
1000 2
0 1 2 1
1 2 1 1
答案:1000
1000 1
0 1 1 1
答案:1000
我的代码
#include<iostream>
#include<iomanip>
#include<cstring>
#include<string>
#include<algorithm>
using namespace std;
struct Rec
{
long long l, t, w, h;//注意不能用int,用long (4分错误)
};
//在某一处的最左边,如果左边没有,就全部直接最边界(对于题中某个话的解释
//这个题的另一个关键,就是只能是整数
int main()
{
//***********输入部分
int r;
cin >> r;
int n;
cin >> n;
Rec re[10000];
int i = 0;
for (i = 0; i < n; i++)
{
cin >> re[i].l >> re[i].t >> re[i].w >> re[i].h;
}
long long min = 0, max = r;//二分查找范围
long long mid = (min + max) / 2;
long long right = 2, left = 1;//左右两侧面积
//******二分查找部分,如果遇到两侧面积相等,则提前结束
while (max>=min)
{
/*****计算面积******/
right = 0, left = 0;
for (i = 0; i < n; i++)
{
if (re[i].l >= mid)
{
right += re[i].w * re[i].h ;
}
else
{
if (re[i].l + re[i].w <= mid)
{
left += re[i].w * re[i].h;
}
else
{
left += (mid - re[i].l) * re[i].h;
right += (re[i].l + re[i].w - mid) * re[i].h;
}
}
}
/*****计算面积结束******/
if (left > right)
{
//max = mid - 1;
max = mid-1;
}
else if (left < right)
min = mid+1 ;
else
break;
mid = (max + min) / 2;
}
//如果二分查找是自然结束的,就令mid=min,并计算新情况下的左右两侧面积;
//否则(即是两侧面积相等的情况),则跳过
if (right != left)
{
mid = min;
/*****计算面积******/
right = 0, left = 0;
for (i = 0; i < n; i++)
{
if (re[i].l >= mid)
{
right += re[i].w * re[i].h;
}
else
{
if (re[i].l + re[i].w <= mid)
{
left += re[i].w * re[i].h;
}
else
{
left += (mid - re[i].l) * re[i].h;
right += (re[i].l + re[i].w - mid) * re[i].h;
}
}
}
/*****计算面积结束******/
}
//****如果右侧矩形面积为0,则可以取极限情况,划分直线 为大矩形的边界
//如果左右侧相等,则说明二分查找是提前结束,尝试是否可以取更大的mid值(有可能矩形中是有一块空的,两侧面积相等
//除此之外即为非特殊情况,直接输出即可
if (right == 0)
{
cout << r;
}
else if (right == left)
{
do
{
++mid;
/*****计算面积******/
right = 0, left = 0;
for (int i = 0; i < n; i++)
{
if (re[i].l >= mid)
{
right += re[i].w * re[i].h;
}
else
{
if (re[i].l + re[i].w <= mid)
{
left += re[i].w * re[i].h;
}
else
{
left += (mid - re[i].l);
right += (re[i].l + re[i].w - mid) * re[i].h;
}
}
}
/*****计算面积结束******/
} while (right == left);
cout << --mid;
}
else
cout << mid;
return 0;
}
参考代码
参考代码中对于矩形面积计算的处理方法很有趣,即他将每一列的面积都存储在一个数组里,这在之后的使用时只需要进行加法计算即可。
这种方法对于小矩形数量较多,且较大的情况比较适用
但对于矩形数量少的地方,可能会有大量的+0操作
可能这就是参考代码用时大约是我的5倍的原因吧
思路:
因为小矩形都在大矩形范围之类,并且互不重叠,坐标又全部是整数型,所以我用一个数组来保存每一列中小矩形所占的面积,然后再使用二分找到k
需要注意的是移动k的条件!具体逻辑见代码
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
int area[1000005]; //每一列的小矩形的面积,如area[0]表示横坐标为0到1之间的小矩形的面积 R最大为1000000
int R; //题目中的R
int N; //题目中的N
int main()
{
scanf("%d%d",&R,&N);
for(int i=0; i<N; i++)
{
//通过输入的四个数据计算出我们需要的数据
int l,t,w,h;
scanf("%d%d%d%d",&l,&t,&w,&h);
for(int i=l; i<l+w; i++)
{
area[i]+=h;
}
}
int l,r,mid;
l=0;
r=R;
long long left=0,right=0; //注意总面积很有可能超出2^31
while(r>l)
{
left=0;
right=0;
mid=(l+r)/2;
for(int i=0; i<mid; i++)
{
left+=area[i];
}
for(int i=mid; i<R; i++)
{
right+=area[i];
}
if(r-l==1) //经过二分后的最后判断
{
if(left>=right) //如果当前左边大于右边
{
if(left+area[l]>=right-area[l] && //如果把k再往右边移动以后仍然满足左边大于右边(可省略)
(left+area[l]-(right-area[l])<=left-right)) //如果把k再往右边移动以后面积差不增加
{
l++; //那就移动吧
}
else
{
break; //不移
}
}
else //如果当前左边已经比右边小了
{
l++; //肯定要移动了
}
}
else //二分中
{
if(left<=right) //为了尽量让k靠右,需要用小于等于
{
l=mid;
}
if(left>right) //为了尽量减少面积差
{
r=mid;
}
}
}
while(l+1<=R&&area[l+1]==0) //考虑到可能会有右边没有矩形的情况,对k进行最后的判断
{
l++;
}
printf("%d",l);
return 0;
}