小奇想画几朵红莲,可惜它刚开始学画画,只能从画圆开始。小奇画了n个圆,它们的圆心都在x轴上,且两两不相交(可以相切)。现在小奇想知道,它画的圆把画纸分割成了多少块?(假设画纸无限大)
输入
第一行包括1个整数n。
接下来n行,每行两个整数x,r,表示小奇画了圆心在(x,0),半径为r的一个圆。
输出
输出一个整数表示答案。
样例输入
4
7 5
-9 11 11 9
0 20
样例输出
6
提示
对于 100%数据,1<=n<=300000,-10^9<=x<=10^9,1<=r<=10^9。
首先我们可以确定,每增加一个圆都会使答案加1。唯一要判断的是,当前的圆的直径区间是否已经被其他圆填满,如果是的话,答案还要加1。
我的思想是用map来维护一个区间是否被其他圆填满了,这个区间的左端点是l,右端点是r,如果这个区间已经被其他圆填满,那么用A[l]=r,A[r]=l表示;每当考虑一个圆的直径区间是否被其他圆填满,我们就看这个圆的左端点和右端点是否关联映射,如果是的话,答案要再加1。每考虑一个圆时,将的左右端点标记,代表这两个点可能成为切点。
维护的过程:首先将圆按照半径从小到大排序(因为判断一个圆的直径区间是否被其他圆填满,肯定是先填小圆,再看大圆)。
然后依次考虑每一个圆,先看左端点是否被标记,如果标记了,那么这个圆和先前的圆外切或内切。我们找出标记的左端点相关联的点(注意是以前的圆而不是现在的圆,A[l]=r,找出r),看它是否小于(圆从小到大排序)正考虑的圆的右端点,如果小于,那么这个圆和先前的圆内切,我们就要更新A[l]和A[r],将他扩大,因为以后的圆只能更大,不会跑到这个圆内。否则,是外切,那么也要更新点,即被其他圆填满的区间变大了。对于右端点的考虑,是一样的。
代码:
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<algorithm>
#include<map>
using namespace std;
typedef pair<int,int>pa;
map<int,int>M,A,C;
struct Node
{
int x;
int r;
} node[300005];
bool cmp(Node a,Node b)
{
return a.r<b.r;
}
int main()
{
int n;
while(~scanf("%d",&n))
{
A.clear();
C.clear();
for(int i=1; i<=n; i++)
{
scanf("%d%d",&node[i].x,&node[i].r);
}
sort(node+1,node+n+1,cmp);//从小到大排序,先看小圆,再看大圆
int ans=1,l,r;
for(int i=1; i<=n; i++)
{
l=node[i].x-node[i].r;//圆的左端点
r=node[i].x+node[i].r;//圆的右端点
ans++;//每多一个圆,就会多划分一个块,答案加1
if(A[l]==r&&A[r]==l)//左端点和右端点是否联通,即中间被其他圆相切填满
{
ans++;//是的话,那么这个圆被分成了两块,答案加1
}
if(!C[l]&&!C[r])//如果左右端点都没有被标记,那就将他们标记
{
A[l]=r;//l和r联通
A[r]=l;
}
else if(C[l]&&!C[r])//左端点标记了,即这个点是之前的某个圆的左或右端点
{
if(A[l]<l)//是外切,将两个圆的联通区间合并
{
A[r]=A[l];
A[A[l]]=r;
}
else//是内切,更新,使得联通区间更大
{
A[l]=r;
A[r]=l;
}
}
else if(C[r]&&!C[l])//考虑右端点,和左端点一样
{
if(A[r]>r)
{
A[l]=A[r];
A[A[r]]=l;
}
else
{
A[l]=r;
A[r]=l;
}
}
else//左右端点都标记了,那么将这三个圆的联通区间合并
{
A[A[l]]=A[r];
A[A[r]]=A[l];
}
C[l]=C[r]=1;//将左右端点标记
}
cout<<ans<<endl;
}
}