矩形相交面积
题目描述
Farmer John has purchased a new machine that is capable ofplanting grass
within any rectangular region of his farm that is "axially aligned"(i.e.,
with vertical and horizontal sides). Unfortunately, the machine
malfunctions one day and plants grass in not one, but N (1 <= N <= 10)
different rectangular regions, some of which may even overlap.
Given the rectangular regions planted with grass, please help FJ compute
the total area in his farm that is now covered with grass.
输入格式 1865.in
* Line 1: The integer N.
* Lines 2..1+N: Each line contains four space-separated integers x1 y1
x2 y2 specifying a rectangular region with upper-left corner
(x1,y1) and lower-right corner (x2,y2). All coordinates are
in the range -10,000...10,000.
输出格式 1865.out
* Line 1: The total area covered by grass.
输入样例 1865.in
2
0 5 4 1
2 4 6 2
输出样例 1865.out
20
这题有两种办法。
方法一、容斥原理。
就像韦恩图一样,我们先将所有矩形面积的面积相加,再减去所有2个矩形的交集部分,加上所有3个矩形的交集部分,再减去所有4个矩形的交集部分……
我们需要枚举相交的矩形,用DFS 就可以实现。那么,怎么计算矩形的交集部分呢?
通过分析可以发现,两个矩形的相交部分,其上边界up、下边界dow分别等于两矩形上边界中较大值,两矩形下边界中较小值,其左le、右ri边界分别等于两矩形左边界较大值,右边界较小值。
只有当up<=down并且le<=ri时,两个矩形才有相交的部分。往后的DFS时,就拿那些矩形不断地跟此小矩形比较,不断缩小范围即可。
时间复杂度:O(2^n)
代码如下:
#include<iostream>
#include<cstdio>
using namespace std;
const int MAXN=15;
int ans,n;
int u[MAXN],d[MAXN],a[MAXN],r[MAXN],l[MAXN];
int dow,up,le,ri,u2,d2,r2,l2;
void dfs(int qd,int c,int k,int up,int dow,int le,int ri)
{
if(le>ri||up>dow) return;
if(c>k)
{
if(k%2==0) ans-=(dow-up)*(ri-le);
else ans+=(dow-up)*(ri-le);
return;
}
for(int i=qd;i<=n;i++)
{
if(c==1) dfs(i+1,c+1,k,u[i],d[i],l[i],r[i]);
else dfs(i+1,c+1,k,max(up,u[i]),min(dow,d[i]),max(le,l[i]),min(ri,r[i]));
}
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>u[i]>>r[i]>>d[i]>>l[i];
ans+=(d[i]-u[i])*(r[i]-l[i]);
}
for(int i=2;i<=n;i++) dfs(1,1,i,0,0,0,0);
cout<<ans<<endl;
return 0;
}
方法二、离散化。
【离散化,是一个非常常用的技巧,可以大大的降低时空复杂度。它的道理,以我的理解就是——只考虑有用的值。为了实现目的,往往可以用排序后处理等手段。
之前的题目中,用到离散化的也不少了。比如SMOJ1247逆序对。那题需要用每个数具体的值来构建线段树,但是数的值高达10^8,强行构建会导致爆内存。不过,数的个数只有10^5。
这样的问题,用离散化可以解决——将数值进行排序,以新的较小下标来代替原本较大的数值,用新的下标构建线段树。实际上,这样做的本质,就是提取有用的东西——虽然数的大小有10^8种可能,但是实际上只有10^5个“有用”的数值(太浪费了)。那么,我只需要将这10^5个有用的数值记录、保存下来即可。由此,大大降低时空复杂度。】
这题,就是用离散化的成功典范。
如果暴力做,可以用染色法:开一个bool数组v[i][j]表示(i,j)这个点是否有被覆盖(一开始初始化为false)。然后遍历n个矩形,将它们所覆盖到的点都置为true。最后遍历所有的点,对v[i][j]=true的点进行计数即可。
时间复杂度:4×10^9。
空间复杂度:4×10^8。
这样做显然不行。
我们发现,尽管坐标的范围很大,但是矩形的数量最多只有10。除了这些矩形可能涉足的格子,其它的都是没用的。
因此,我们只要处理这些矩形可能涉足的格子。将矩形的左右边界及上下边界分别存储起来,进行排序。然后,枚举小矩形的左、上边界,可以得知右、下边界,检查这个小矩形是否在题目的矩形中。如果在,就将此小矩形的面积计入答案中。
代码如下:
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int n;
const int MAXN=15;
long long ans=0;
struct sq
{
long long up,down;
long long le,ri;
}a[MAXN];
long long x[2*MAXN],y[2*MAXN];
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i].le>>a[i].up;//
cin>>a[i].ri>>a[i].down;
x[2*i-1]=a[i].le;
x[2*i]=a[i].ri;
y[2*i-1]=a[i].down;
y[2*i]=a[i].up;
}
sort(x+1,x+1+2*n);
sort(y+1,y+1+2*n);
for(int i=1;i<2*n;i++)
{
for(int j=1;j<2*n;j++)
{
long long s=(x[i+1]-x[i])*(y[j+1]-y[j]),ok=0;
for(int r=1;r<=n;r++)
{
if(a[r].le<=x[i]&&x[i+1]<=a[r].ri&&a[r].down<=y[j]&&a[r].up>=y[j+1])
{
ans+=s;
ok=1;
break;
}
}
}
}
cout<<ans<<endl;
return 0;
}