2016级算法第二次上机-A.画个圈圈诅咒你
890 画个圈圈诅咒你
思路
简单题。题目中的圆并没有什么实际作用,简化成线段重合问题会更好理解些。
暴力解法:使用双重for循环会T到想哭,记住最直接的方法一般是过不了题的。
解法一:二分查找。空间较小,时间更长。
把圆相离的问题转换为线段相交的问题,按先起点后终点的顺序升序排列这些圆(线段)。对于每条线段,向右找到第一条起点比这条线段终点大的线段,然后后面的线段都会满足要求,这里用二分去找。具体参考参考代码一。
解法二:线性查找。时间更短,空间更大。
同样是把圆相离的问题转换为线段的相交问题,把所有圆的左点和右点记录下来,并标记他们是左还是右,点的数量是圆的数量的两倍。排序:按所有点的位置排,如果点位置一样,则左边点优先(重要)。从头到尾遍历一次,用一变量(初始值为n)记录右边有多少个圆的左点,遇到左点时变量减1,遇到右点时用答案加上当前变量值,即是此圆右边与之相离的数量(左边的不须计算否则会产生重复)。具体参考参考代码二。
分析
两种方法都需要排序,排序时间 \(O(NlogN)\) 。
查找时间:解法一是 \(O(NlogN)\),解法二是 \(O(N)\) 。
参考代码一
//
// Created by AlvinZH on 2017/10/24.
// Copyright (c) AlvinZH. All rights reserved.
//
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
int n, x, r;
struct Circle {
int left, right;
bool operator < (const Circle a) const {
if(left == a.left) return right < a.right;//其次右端
return left < a.left;//左端优先
}
}C[50005];
//二分查找右边最近的圆
int find(int l, int r, int x)
{
int m;
while(l <= r)
{
m = (l + r) >> 1;
if(C[m].left < x) l = m + 1;
else if(C[m].left >= x) r = m - 1;
}
return l;
}
int main()
{
while(~scanf("%d",&n))
{
for(int i = 0; i < n; ++i) {
scanf("%d %d", &x, &r);
C[i].left = x - r;
C[i].right = x + r;
}
sort(C, C+n);
int ans=0;
for(int i = 0; i < n-1; i++) {
ans += n - find(i+1, n-1, C[i].right+1);
}
printf("%d\n", ans);
}
}
参考代码二
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
int n, x, r;
struct Node {
int x;//位置
int isR;//标记左右
bool operator < (const Node n) const {
if(x < n.x) return true;
else if(x == n.x && isR == 0) return true;
return false;
}
}N[100010];
int main()
{
while(~scanf("%d", &n))
{
int num = 0;
for (int i = 0; i < n; ++i) {
scanf("%d %d", &x, &r);
N[num].x = x - r;
N[num].isR = 0;
num++;
N[num].x = x + r;
N[num].isR = 1;
num++;
}
sort(N, N+num);
int ans = 0;
int sum = n;
for (int i = 0; i < num; ++i) {
if(N[i].isR == 0) sum--;
else ans += sum;
}
printf("%d\n", ans);
}
}