Circles dividing the plane

23

5

Task

You will be given a set of circles in the plane with their centers on the line y=0. It is guaranteed that no pair of circles has more than one common point.

Your task is to determine into how many regions into which the circles divide the plane. A region is an inclusion-maximal contiguous set of points not intersecting any of the circles.

You should write a program that computes this answer when given a description of the circles.


Here's an example:

example 1

On the left side you see the circles drawn in the plane. However, in the right half of the picture, the regions produced by the circles are colored distinctly (one color per region). There are six regions in this example.


Input

The first line of the input contains a number, N, the number of circle descriptions to follow. This line is optional, if your solution works without it, it's fine.

The following N lines each contain two integers, xi and ri > 0, representing a circle with center (xi, 0) and radius ri.

It is guaranteed that no pair of circles has more than one common point. It is further guaranteed that xi and ri do not exceed 10^9 in absolute value (so they comfortably fit into a 32-bit integer).


The input may be:

  • read from STDIN

  • read from a file named I in the current directory

Alternatively, the input could be:

  • available as a string (including newlines) in a global variable

  • on the stack


Output

This should be a single integer, the number to regions produced. This should be written to STDOUT or a file named O in the current directory.


Rules

  • Shortest code in bytes wins

  • +200 byte penalty if your code does not have a runtime + space complexity polynomial in n

  • -100 byte bonus for worst-case expected runtime + space complexity O(n log n)

  • -50 byte bonus for worst-case expected runtime + space complexity O(n)

  • -100 byte bonus for deterministic runtime + space complexity O(n)

While assessing the runtime:

  • Assume that hash tables have O(1) expected runtime for insert, delete and lookup, regardless of the sequence of operations and the input data. This may or may not be true, depending on whether the implementation uses randomization.

  • Assume that the builtin sort of your programming language takes deterministic O(n log n) time, where n is the size of the input sequence.

  • Assume that arithmetic operations on input numbers take only O(1) time.

  • Do not assume that input numbers are bound by a constant, although, for practical reasons, they are. This means that algorithms like radix sort or counting sort are not linear time. In general, very large constant factors should be avoided.


Examples

Input:

2 
1 3
5 1

Output: 3


Input:

3
2 2
1 1
3 1

Output: 5

4
7 5
-9 11
11 9
0 20

Input:

9
38 14
-60 40
73 19
0 100
98 2
-15 5
39 15
-38 62
94 2

Output: 11


Hints

We can use the following idea for a very compact solution. Lets intersect the set of circles with the X axis and interpret the intersection points as nodes in a planar graph:

enter image description here

Every circle produces exactly 2 edges in this graph and up to two nodes. We can count the number of nodes by using a hash table to keep track of the total number of distinct left or right borders.

Then we can use the Euler characteristic formula to compute the number of faces of a drawing of the graph:

V - E + F - C = 1

F = E - V + C + 1

To compute C, the number of connected components, we can use a depth-first search.


Note: This problem idea is borrowed from a recent Croatian programming contest, but please don't cheat by looking at the solution outlines. :)

Niklas B.

Posted 2014-03-26T20:04:34.543

Reputation: 477

Are some of those bonuses decoys? – user2357112 supports Monica – 2014-03-26T23:02:44.983

@user2357112 Don't assume it can't be done unless you can prove it ;) – Niklas B. – 2014-03-26T23:28:50.143

Well, with inputs guaranteed to fit in a machine integer, we could use a radix sort and call it O(n). I hate assuming restricted input size, because strictly speaking, it means there are finitely many possible inputs. – user2357112 supports Monica – 2014-03-26T23:46:05.730

@user2357112 No, I said you cannot assume the integers to be bounded while assessing the asymptotics, so neither radix sort nor counting sort would be linear time and space. That they fit into a word is just to make arithmetics "real" O(1) and for practical reasons (bounded variable width in most languages) – Niklas B. – 2014-03-26T23:47:56.737

@NiklasB. if I have an algorithm in which the only component with superlinear complexity is the sorting, to I have to implement merge sort if my language uses quick sort, in order to get the n log n bonus? Also, I do have new conceptually new solution. Should I post a new answer of replace the old one? (I'd prefer the former, in case my new solution isn't actually correct) – Martin Ender – 2014-03-27T12:09:18.010

@m.buettner no, sorting is fine, you will grt the bonus. Yes, you can make a new answer – Niklas B. – 2014-03-27T13:53:09.133

@NiklasB. Please tell me what bonus I might get below. I think it's -150, but is it -250? – Not that Charles – 2014-03-28T13:48:03.423

Answers

2

Mathematica, 125 122 - 150 = -28 chars

I don't know the complexity of the built-in function ConnectedComponents.

1+{-1,2,1}.Length/@{VertexList@#,EdgeList@#,ConnectedComponents@#}&@Graph[(+##)<->(#-#2)&@@@Rest@ImportString[#,"Table"]]&

Usage:

1+{-1,2,1}.Length/@{VertexList@#,EdgeList@#,ConnectedComponents@#}&@Graph[(+##)<->(#-#2)&@@@Rest@ImportString[#,"Table"]]&[
"9
38 14
-60 40
73 19
0 100
98 2
-15 5
39 15
-38 62
94 2"]

11

alephalpha

Posted 2014-03-26T20:04:34.543

Reputation: 23 988

+1, for golfing with less than 0 chars ;) – Aᴄʜᴇʀᴏɴғᴀɪʟ – 2016-01-09T04:11:17.723

I think we can safely assume that ConnectedComponents has linear expected worst-case complexity, so if there are O(n) components, this would be fine. I don't understand Mathematica, so I can't tell whether it is O(n) overall and qualifies for the -150 bonus? I think it does. Do I just run it with input in STDIN? – Niklas B. – 2014-03-29T15:51:29.677

@NiklasB. his input method is just passing a string variable to an anonymous function. so I think that should qualify. as for the output, in Mathematica this will simply result in the number ending up in console output, so that should probably be fine, too. – Martin Ender – 2014-03-29T15:57:01.537

I've verified the correctness of this, so I think with a score of -28 this is the new leader. Congratulations! – Niklas B. – 2014-03-29T16:17:01.177

@NiklasB. why only 150? Which part of the algorithm has superlinear worst-case complexity? – Martin Ender – 2014-03-29T17:17:45.050

@m.buettner 150 is for O(n) expected time. For graphs with arbitrary numbers as nodes, implicitly defined like here, you just can't find the number of CCs in linear time, which can be shown by reducing element distinctness to connected components. I think we can also reduce element distinctness to the original problem

– Niklas B. – 2014-03-29T17:32:55.830

@NiklasB. isn't the time complexity for finding connected components in general O(V+E)? In our case E is equal to n and V is at most 2n. So shouldn't we get O(3n) = O(n)? – Martin Ender – 2014-03-29T17:38:03.093

@m.buettner Even without any edges, you need to solve element distinctness (find the number of unique elements), so no, that's not possible. O(V+E) assumes that you can check in O(1) if you have already visited a node, which is generally not possible – Niklas B. – 2014-03-29T17:39:45.230

@NiklasB. ah I see, thanks for the clarification. – Martin Ender – 2014-03-29T17:40:24.863

4

Ruby - 312 306 285 273 269 259 characters

This answer has been superseded by my other approach which uses considerably less characters and runs in O(n log n).

Okay, let's go. For starters, I just wanted a working implementation, so this is not algorithmically optimised yet. I sort the circles from largest to smallest, and build a tree (circles included in other circles are children of those larger ones). Both operations take O(n^2) at worst and O(n log n) at best. Then I iterate through the tree to count areas. If the children of a circle fill up its entire diameter, there are two new areas, otherwise there is just one. This iteration take O(n). So I have overall complexity O(n^2) and qualify for neither reward nor penalty.

This code expects the input without the number of circles to be stored in a variable s:

t=[]
s.lines.map{|x|x,r=x.split.map &:to_i;{d:2*r,l:x-r,c:[]}}.sort_by!{|c|-c[:d]}.map{|c|i=-1;n=t
while o=n[i+=1]
if 0>d=c[:l]-o[:l]
break
elsif o[:d]>d
n=o[:c]
i=-1
end
end
n[i,0]=c}
a=1
t.map &(b=->n{d=0
n[:c].each{|c|d+=c[:d]}.map &b
a+=d==n[:d]?2:1})
p a

Ungolfed version (expects input in variable string):

list = []
string.split("\n").map { |x|
  m = x.split
  x,radius = m.map &:to_i
  list<<{x:x, d:2*radius, l:x-radius, r:x+radius, children:[]}
}
list.sort_by! { |circle| -circle[:d] }
tree = []
list.map { |circle|
  i = -1
  node = tree
  while c=node[i+=1]
    if circle[:x]<c[:l]
      break
    elsif circle[:x]<c[:r]
      node = c[:children]
      i = -1
    end
  end
  node[i,0] = circle
}
areas = 1
tree.map &(count = -> node {
  d = 0
  i = -1
  while c=node[:children][i+=1]
    count.call c
    d += c[:d]
  end
  areas += d == node[:d] ? 2 : 1
})
p areas

Martin Ender

Posted 2014-03-26T20:04:34.543

Reputation: 184 808

@NiklasB. yes that test case would be nice. The relation that defines the edges in my tree is simply inclusion of one circle in another. Since on circle can't be contained in two circles that don't contain each other (due to the "one intersection" condition), I don't see how this could be a DAG. – Martin Ender – 2014-03-27T00:00:44.030

Are a node's grandchildren also its children? – user2357112 supports Monica – 2014-03-27T00:05:44.673

@user2357112 no, because they can only split their direct parent – Martin Ender – 2014-03-27T00:06:59.783

@NiklasB. If I run it with the example in your question, I get 11. For the one in your comment 9. – Martin Ender – 2014-03-27T00:21:40.583

@NiklasB. wait, I actually get 10 and 8 with my ungolfed version, but 11 and 9 with my current golfed version :D – Martin Ender – 2014-03-27T00:22:37.513

Also your golfed version would need to output the value, so probably you need an additional 2 bytes – Niklas B. – 2014-03-27T00:28:08.207

@NiklasB. oh, if you make it a function body in ruby, then the last evaluated expression is returned, which is why I added that a at the end. If you actually want stdout then it's two more bytes for adding p and a space. But since you said you weren't fussed about I/O I assumed, having the result in a would be enough (and I even added the two bytes for the a at the end so it could be used as a function body). Sooooo, could you clarify the rules on that? :D PS: found the bug – Martin Ender – 2014-03-27T00:31:50.717

I changed the input part, not the output part. Somehow the program needs to write its result, either to STDOUT or file – Niklas B. – 2014-03-27T00:33:19.183

@NiklasB. alright, it wasn't mentioned in the question. I'll add it. – Martin Ender – 2014-03-27T00:34:38.607

Cheers, that's already a good start :) Nice solution – Niklas B. – 2014-03-27T00:35:05.697

2

Ruby, 203 183 173 133 - 100 = 33 characters

So here is a different approach. This time, I sort the circles by their left-most point. Circles touching at their left-most point are sorted from largest to smallest. This takes O(n log n) (well, Ruby uses quick sort, so actually O(n^2) but implementing merge/heap sort is probably beyond the scope of this challenge). Then I iterate over this list, remembering all left-most and right-most positions of the circles I have visited. This allows me to detect if a series of circles connects all the way across an enclosing larger circle. In this case, there are two subareas, otherwise just one. This iteration takes only O(n) giving a total complexity of O(n log n) which qualifies for the 100 character reward.

This snippet expects the input to be supplied via a file in the command-line arguments without the number of circles:

l,r={},{}
a=1
$<.map{|x|c,q=x.split.map &:to_r;[c-q,-2*q]}.sort.map{|x,y|a+=r[y=x-y]&&l[x]?2:1
l[y]=1 if l[x]&&!r[y]
l[x]=r[y]=1}
p a

Ungolfed version (expects input in a variable string):

list = []
string.split("\n").map { |x|
  m = x.split
  x,radius = m.map &:to_r
  list<<{x:x, d:2*radius, l:x-radius, r:x+radius}
}
list.sort_by! { |circle| circle[:l] + 1/circle[:d] }
l,r={},{}
areas = 1
list.map { |circle|
  x,y=circle[:l],circle[:r]
  if l[x] && r[y]
    areas += 2
  else
    areas += 1
    l[y]=1 if l[x]
  end
  r[y]=1
  l[x]=1
}
p areas

Martin Ender

Posted 2014-03-26T20:04:34.543

Reputation: 184 808

Unfortunately this fails for all the larger testcases. It is fast though ;) I don't have a small failing example this time, but you can find the test data on the contest website I linked to (it's contest #6) – Niklas B. – 2014-03-27T15:34:45.587

The first failing testcase is kruznice/kruznice.in.2 which is the first "real" test case so I assume there is something fundamentally wrong woth the algorithm or implementation. Does it work correct with arbitrarily nested circles? – Niklas B. – 2014-03-27T15:38:55.583

@NiklasB. thanks, I'll have a look into the test cases (might take me until tomorrow night to fix it though) – Martin Ender – 2014-03-27T15:41:09.817

@NiklasB. I figured out the problem and the minimum failing example requires 5 circles: -1 1 1 1 0 2 4 2 0 6. I'll think about how to fix this by tomorrow night (hopefully). – Martin Ender – 2014-03-27T18:20:59.633

Your complexity analysis seems to assume that associative array access is constant time. That seems unlikely to be true in the worst case. – Peter Taylor – 2014-03-27T18:29:53.230

@NiklasB. I was actually able to fix it quite quickly. I don't have the time right now to run it against the entire test suite. If you don't get around to do that, I'll try myself tomorrow. – Martin Ender – 2014-03-27T18:29:57.397

@PeterTaylor Hm yes I did assume a perfect hash map, that's true. I'll need to find some data about the complexity of Ruby's implementation there. Then again, the keys are only integers, so I could as well use a simple array, offset by the left-most position. – Martin Ender – 2014-03-27T18:30:47.523

Then you run into problems with the time taken allocating the array. In C you might be able to claim that you can allocate an arbitrarily sized array in constant time, but in any language which zeros the contents you can't. – Peter Taylor – 2014-03-27T18:33:26.867

@PeterTaylor I see, my complexity would then actually depend on the size of circles, such that doubling a given problem would actually double the required storage and run-time. Hmmm. I'll have to think on this. – Martin Ender – 2014-03-27T18:34:56.950

@PeterTaylor note that even if hashing takes O(log n) the overall complexity is still O(n log n). – Martin Ender – 2014-03-27T18:41:14.960

@Peter I think since associative arrays can easily implemented with O(log n) worst case, this is perfectly fine. I updated the question so that expected O(n log n) worst case is explicitely allowed. My intention was not to disallow randomized solutions using hashing or quicksort, which even in the worst case have a good expected run time. – Niklas B. – 2014-03-27T18:47:50.213

Clarified the question – Niklas B. – 2014-03-27T18:55:06.930

The program now passes all but one test case (kruznice.in.6) Maybe a precision problem? – Niklas B. – 2014-03-27T18:57:51.027

Yep, it's due to the use of floating point numbers. I have a fix which leaves the byte count intact but is precise. – Niklas B. – 2014-03-27T18:59:09.427

@NiklasB. fixed it, saving two bytes. :) – Martin Ender – 2014-03-28T22:54:59.127

What? Ruby has rationals? Nice. Now this is the current leader :) – Niklas B. – 2014-03-28T22:58:32.620

@NiklasB. in terms of complexity or golfing? :) – Martin Ender – 2014-03-28T23:00:11.420

Both actually :) – Niklas B. – 2014-03-28T23:01:57.003

@NiklasB. I thought so :D ... I'll see what I can think of. – Martin Ender – 2014-03-28T23:02:25.400

A few suggestions: You can use tuple unpacking at two places in the lambda argument lists and you could use $<.each instead of s.lines.map. And you use c[1] instead of x at one place – Niklas B. – 2014-03-28T23:07:07.250

And the ! in sort_by! is superfluous :) – Niklas B. – 2014-03-28T23:11:42.593

@NiklasB. right, $< assumes file/console input... I could probably change that. I had already fixed the tuple unpacking. And I didn't find sort_by in the doc, because I only looked at Array and not Enumerable. Thanks for the hints! :) – Martin Ender – 2014-03-28T23:16:20.360

I have another suggestion that brings the size down by around 10 bytes: It involves using sort instead of sort_by and making use of the lexicographical order that Ruby uses for comparing arrays. Ruby is a pretty cool language :) Almost as nasty as Perl – Niklas B. – 2014-03-28T23:43:09.003

@NiklasB. sweet! :D ... I'm actually still learning a lot of Ruby basics. This is the second thing I've ever written in Ruby (with the first one being another challenge from earlier this week). – Martin Ender – 2014-03-28T23:53:45.983

1

Julia - 260 -100(bonus?) = 160

Interpreting the circles as figures with vertices (intersections), edges, and faces (areas of the plane) we can relate each other using Euler characteristic, so we only need to know the number of "vertices" and "edges" to have the number of "faces" or regions of the plane with the formula written below: Euler characteristic

UPDATE: By thinking a while i figured out that the problem with my method was only when circles where not connected, so i came with an idea, why not artificially connect them? So the whole will satisfy the Euler formula.

Circles Example

F = 2+E-V (V=6, E=9)

[Dont work with nested circles, so its not an answer of the problem for general cases]

Code:

s=readlines(open("s"))
n=int(s[1])
c=zeros(n,2)
t=[]
for i=1:n
    a=int(split(s[i+1]))
    c[i,1]=a[1]-a[2]
    c[i,2]=a[1]+a[2]
    if i==1 t=[c[1]]end
    append!(t,[c[i,1]:.5:c[i,2]])
end
e=0
t=sort(t)
for i in 1:(length(t)-1) e+=t[i+1]-t[i]>=1?1:0end #adds one edge for every gap
2+2n+e-length(unique(c)) # 2+E-V = 2+(2n+e)-#vertices

CCP

Posted 2014-03-26T20:04:34.543

Reputation: 632

I think your program will fail for 2 -10 1 10 1. – Niklas B. – 2014-03-27T00:39:45.683

"It is guaranteed that no pair of circles has more than one common point." so i think it will not apply :) – CCP – 2014-03-27T00:44:01.567

@CCP They have zero common points. n=2, the circles are (C=(-10,0), r=1) and (C=(10,0), r=1) – Niklas B. – 2014-03-27T00:44:34.397

@NiklasB. Didnt see the "-"! And You're right, but the idea i think it was right, let me fix the problem and i will edit the answer. – CCP – 2014-03-27T00:48:10.047

1Doesn't the graph have to be connected to apply the Euler characteristic? – user2357112 supports Monica – 2014-03-27T01:13:35.107

Thats why when its not connected i subtract 1. – CCP – 2014-03-27T01:26:32.917

What if there are 5 connected components? – user2357112 supports Monica – 2014-03-27T01:27:40.263

Now works with all the example cases and "2 -10 1 10 1" case. – CCP – 2014-03-27T01:40:05.830

Well yeah, but not for the general case. Check the comments above – Niklas B. – 2014-03-27T01:40:47.953

Can you write an example with answer to see if its true? – CCP – 2014-03-27T01:42:06.367

As I said, the problematic case is already described in a comment here. Maybe you can generalize the -10 1 10 1 sample so that it breaks your program. Look closely at the variables involved in your formula – Niklas B. – 2014-03-27T01:42:51.827

I dont see any problem with 5 connected circles, the output its 6 like it should be(?) Graph: 00000 – CCP – 2014-03-27T01:51:26.837

You should look more in the direction of non-connected circles – Niklas B. – 2014-03-27T01:52:20.377

"5 connected components" means "5 parts of the graph that are connected within themselves, but not connected to each other". 5 0 1 10 1 20 1 30 1 40 1 would be an example of that. – user2357112 supports Monica – 2014-03-27T01:54:31.887

Ok , i think i solved that case! ;) – CCP – 2014-03-27T01:58:50.313

Also whitespace counts for this challenge, so your current version has 238 bytes – Niklas B. – 2014-03-27T02:00:02.717

I used http://bytecount.bluebus112.com/ to count, why my solution didnt win the other bonuses?

– CCP – 2014-03-27T02:00:48.713

That webapp doesn't count newlines and leading whitespace it seems. Use wc or any file browser to see the size in bytes – Niklas B. – 2014-03-27T02:02:10.107

If it is O(n log n), you get -150. But currently it is not correct. – Niklas B. – 2014-03-27T02:02:47.850

The solution or the speed? – CCP – 2014-03-27T02:05:17.117

The solution is not correct. Also your program doesn't give any output when I run it – Niklas B. – 2014-03-27T02:05:49.113

Why the -1?. I already edited the answer solving the 5 ring problem, generalizating for n ring. Its another case where my solution its wrong? (Julia diplays last line statement in console) – CCP – 2014-03-27T02:07:16.030

Yes, there are lots of cases where the program fails. The basic idea is interesting, but your implementation is not correct – Niklas B. – 2014-03-27T02:07:39.927

Well this problem was very fun and i think that the idea of using Euler characteristic its intresting. But i think iam not able to see the cases you are talking about. So i think i'll leave the problem unfinished :(. – CCP – 2014-03-27T02:14:24.410

I could give you a large testcase where it fails, but I don't think that would help you a lot. Maybe you can use the other correct solution from above and test your program against it with random input? – Niklas B. – 2014-03-27T02:17:07.547

1Ah, here is a simple case: 4 0 2 1 1 10 2 11 1 But I don't think I can give you a lot more test cases, that would be a bit unfair – Niklas B. – 2014-03-27T02:22:41.930

Yes i think you're right.. The formula works with connected circles, if they are not it will give some other cases where the algorithm fails (F=2+E-V). The instersection problem will not be difficult to solve, but i think i need to change all the algorithm to do it. So i think i will not continue to improve the answer, what would be the better way to proceed? delete the answer or let it as unfinished?. – CCP – 2014-03-27T02:54:24.837

Does this work for arbitrarily nested circles? It looks like it doesn't – Niklas B. – 2014-03-27T15:27:21.100

Sorry to say, but this still does not work for the general case. Remember that circles can be arbitrarily nested and the same problems can occur inside a large circle – Niklas B. – 2014-03-27T15:31:28.693

1

Spidermonkey JS, 308, 287, 273 - 100 = 173

I think if I rewrote this in Ruby or Python I could save characters.

Code:

for(a=[d=readline],e={},u=d(n=1);u--;)[r,q]=d().split(' '),l=r-q,r-=-q,e[l]=e[l]||[0,0],e[r]=e[r]||[0,0],e[r][1]++,e[l][0]++
for(k=Object.keys(e).sort(function(a,b)b-a);i=k.pop();a.length&&a.pop()&a.push(0)){for([l,r]=e[i];r--;)n+=a.pop()
for(n+=l;l--;)a.push(l>0)}print(n)

Algorithm:

n = 1 // this will be the total
e = {x:[numLeftBounds,numRightBounds]} // imagine this as the x axis with a count of zero-crossings
a = [] // this is the stack of circles around the "cursor".  
       // values will be 1 if that circle's never had alone time, else 0
k = sort keys of e on x
for each key in k: // this is the "cursor"
  n += key[numLeftBounds] // each circle that opens has at least one space.
  k[numRightBounds].times {n += a.pop()} // pop the closing circles. if any were never alone, add 1
  k[numLeftBounds].times {a.push(alwaysAlone)} // push the opening circles
  if !a.empty():
     set the innermost circle (top of stack) to false (not never alone)
  fi
loop

I'm not terribly great at big O notation, but I think that's O(n) since I'm effectively looping through each circle 3 times (create, left, right) and also sorting the map's keys (and I sort for O(n log n) but that disappears). Is this deterministic?

Not that Charles

Posted 2014-03-26T20:04:34.543

Reputation: 1 905

If anyone has any advice on how to combine the r loop and the l loop into one statement, i'd appreciate it. – Not that Charles – 2014-03-28T12:55:59.990

Cheers :) Looks to me like your runtime is indeed O(n log n), due to the sort, which would be -100. I will let you know whether it passes all test cases. – Niklas B. – 2014-03-28T14:00:56.250

Nice, your program passes all test cases on the first try. I think something like this (sweepline with some state maintained in a stack) was the official solution from the problem authors. Your program gets a total score of 173 – Niklas B. – 2014-03-28T14:56:24.863

@NiklasB. Thanks! – Not that Charles – 2014-03-28T18:03:39.930

I was trying a much more robust solution with overlapping circles.... then I saw that they could only intersect at one point. – Not that Charles – 2014-03-28T18:48:44.200

sorry about that, but I thought I had made that clear enough – Niklas B. – 2014-03-28T19:38:58.207

@NiklasB. You did. No worries. It was over-engineering on my part – Not that Charles – 2014-03-28T19:39:47.083

I emphasized that part a bit more. Hope you still enjoyed solving the problem :) – Niklas B. – 2014-03-28T19:48:00.837

-1

JavaScript (ES6) - 255 254 Characters - 100 250 Bonus = 155 4

R=/(\S+) (\S+)/ym;N=1;i=w=l=0;for(X=[];m=R.exec(S);){X[N++]={w:j=m[2]*1,l:k=m[1]-j,r:k+2*j};l=k<l?k:l;w=j<w?w:j}M=[];X.map(x=>M[(x.l-l+1)*w-x.w]=x);s=[];M.map(x=>{while(i&&s[i-1].r<x.r)N+=s[--i].w?0:1;i&&(s[i-1].w-=x.w);s[i++]=x});while(i)N+=s[--i].w?0:1

Assumes that the input string is in the variable S and outputs the number of regions to the console.

R=/(\S+) (\S+)/ym;                  // Regular expression to find centre and width.
N=1;                                // Number of regions
w=l=0;                              // Maximum width and minimum left boundary.
X=[];                               // A 1-indexed array to contain the circles.
                                    // All the above are O(1)
for(;m=R.exec(S);){                 // For each circle
    X[N++]={w:j=m[2]*1,l:k=m[1]-j,r:k+2*j};
                                    // Create an object with w (width), l (left boundary)
                                    // and r (right boundary) attributes.
    l=k<l?k:l;                      // Update the minimum left boundary.
    w=j<w?w:j                       // Update the maximum width.
}                                   // O(1) per iteration = O(N) total.
M=[];                               // An array.
X.map(x=>M[(x.l-l+1)*w-x.w]=x);     // Map the 1-indexed array of circles (X) to a
                                    // sparse array indexed so that the elements are
                                    // sorted by ascending left boundary then descending
                                    // width.
                                    // If there are N circles then only N elements are
                                    // created in the array and it can be treated as if it
                                    // is a hashmap (associative array) with a built in
                                    // ordering and as per the rules set in the question
                                    // is O(1) per insert so is O(N) total cost.
                                    // Since the array is sparse then it is still O(N)
                                    // total memory.
s=[];                               // An empty stack
i=0;                                // The number of circles on the stack.
M.map(x=>{                          // Loop through each circle
    while(i&&s[i-1][1]<x[1])        // Check to see if the current circle  is to the right
                                    // of the circles on the stack;
      N+=s[--i][0]?0:1;             // if so, decrement the length of the stack and if the
                                    // circle that pops off has radius equal to the total
                                    // radii of its children then increment the number of
                                    // regions by 1.

                                    // Since there can be at most N items on the stack then
                                    // there can be at most N items popped off the stack
                                    // over all the iterations; therefore this operation
                                    // has an O(N) total cost.
    i&&(s[i-1][0]-=x[0]);           // If there is a circle on the stack then this circle
                                    // is its child. Decrement the parent's radius by the
                                    // current child's radius.
                                    // O(1) per iteration
    s[i++]=x                        // Add the current circle to the stack.
  });
while(i)N+=s[--i][0]?0:1            // Finally, remove all the remaining circles from the
                                    // stack and if the circle that pops off has radius
                                    // equal to the total radii of its children then
                                    // increment the number of regions by 1.
                                    // Since there will always be at least one circle on the
                                    // stack then this has the added bonus of being the final
                                    // command so the value of N is printed to the console.
                                    // As per the previous comment on the complexity, there
                                    // can be at most N items on the stack so between this
                                    // and the iterations over the circles then there can only
                                    // be N items popped off the stack so the complexity of
                                    // all these tests on the circles on the stack is O(N).

The time complexity is now O(N).

Test Case 1

S='2\n1 3\n5 1';
R=/(\S+) (\S+)/ym;N=1;i=w=l=0;for(X=[];m=R.exec(S);){X[N++]={w:j=m[2]*1,l:k=m[1]-j,r:k+2*j};l=k<l?k:l;w=j<w?w:j}M=[];X.map(x=>M[(x.l-l+1)*w-x.w]=x);s=[];M.map(x=>{while(i&&s[i-1].r<x.r)N+=s[--i].w?0:1;i&&(s[i-1].w-=x.w);s[i++]=x});while(i)N+=s[--i].w?0:1

Outputs: 3

Test Case 2

S='3\n2 2\n1 1\n3 1';
R=/(\S+) (\S+)/ym;N=1;i=w=l=0;for(X=[];m=R.exec(S);){X[N++]={w:j=m[2]*1,l:k=m[1]-j,r:k+2*j};l=k<l?k:l;w=j<w?w:j}M=[];X.map(x=>M[(x.l-l+1)*w-x.w]=x);s=[];M.map(x=>{while(i&&s[i-1].r<x.r)N+=s[--i].w?0:1;i&&(s[i-1].w-=x.w);s[i++]=x});while(i)N+=s[--i].w?0:1

Outputs: 5

Test Case 3

S='4\n7 5\n-9 11\n11 9\n0 20';
R=/(\S+) (\S+)/ym;N=1;i=w=l=0;for(X=[];m=R.exec(S);){X[N++]={w:j=m[2]*1,l:k=m[1]-j,r:k+2*j};l=k<l?k:l;w=j<w?w:j}M=[];X.map(x=>M[(x.l-l+1)*w-x.w]=x);s=[];M.map(x=>{while(i&&s[i-1].r<x.r)N+=s[--i].w?0:1;i&&(s[i-1].w-=x.w);s[i++]=x});while(i)N+=s[--i].w?0:1

Outputs: 6

Test Case 4

S='9\n38 14\n-60 40\n73 19\n0 100\n98 2\n-15 5\n39 15\n-38 62\n94 2';
R=/(\S+) (\S+)/ym;N=1;i=w=l=0;for(X=[];m=R.exec(S);){X[N++]={w:j=m[2]*1,l:k=m[1]-j,r:k+2*j};l=k<l?k:l;w=j<w?w:j}M=[];X.map(x=>M[(x.l-l+1)*w-x.w]=x);s=[];M.map(x=>{while(i&&s[i-1].r<x.r)N+=s[--i].w?0:1;i&&(s[i-1].w-=x.w);s[i++]=x});while(i)N+=s[--i].w?0:1

Outputs: 11

Test Case 5

S='87\n-730 4\n-836 2\n-889 1\n-913 15\n-883 5\n-908 8\n-507 77\n-922 2\n-786 2\n-782 2\n-762 22\n-776 2\n-781 3\n-913 3\n-830 2\n-756 4\n-970 30\n-755 5\n-494 506\n-854 4\n15 3\n-914 2\n-840 2\n-833 1\n-505 75\n-888 10\n-856 2\n-503 73\n-745 3\n-903 25\n-897 1\n-896 2\n-848 10\n-878 50\n-864 2\n0 1000\n-934 6\n-792 4\n-271 153\n-917 1\n-891 3\n-833 107\n-847 3\n-758 2\n-754 2\n-892 2\n-738 2\n-876 2\n-52 64\n-882 2\n-270 154\n-763 3\n-868 72\n-846 4\n-427 3\n-771 3\n-767 17\n-852 2\n-765 1\n-772 6\n-831 1\n-582 2\n-910 6\n-772 12\n-764 2\n-907 9\n-909 7\n-578 2\n-872 2\n-848 2\n-528 412\n-731 3\n-879 1\n-862 4\n-909 1\n16 4\n-779 1\n-654 68\n510 490\n-921 3\n-773 5\n-653 69\n-926 2\n-737 3\n-919 1\n-841 1\n-863 3';
R=/(\S+) (\S+)/ym;N=1;i=w=l=0;for(X=[];m=R.exec(S);){X[N++]={w:j=m[2]*1,l:k=m[1]-j,r:k+2*j};l=k<l?k:l;w=j<w?w:j}M=[];X.map(x=>M[(x.l-l+1)*w-x.w]=x);s=[];M.map(x=>{while(i&&s[i-1].r<x.r)N+=s[--i].w?0:1;i&&(s[i-1].w-=x.w);s[i++]=x});while(i)N+=s[--i].w?0:1

Outputs: 105

Previous Version

C=S.split('\n');N=1+C.shift()*1;s=[];C.map(x=>x.split(' ')).map(x=>[w=x[1]*1,x[i=0]*1+w]).sort((a,b)=>(c=a[1]-2*a[0])==(d=b[1]-2*b[0])?b[0]-a[0]:c-d).map(x=>{while(i&&s[i-1][1]<x[1])N+=s[--i][0]?0:1;i&&(s[i-1][0]-=x[0]);s[i++]=x});while(i)N+=s[--i][0]?0:1

With comments:

C=S.split('\n');                    // Split the input into an array on the newlines.
                                    // O(N)
N=1+C.shift()*1;                    // Remove the head of the array and store the value as
                                    // if there are N disjoint circles then there will be
                                    // N+1 regions.
                                    // At worst O(N) depending on how .shift() works.
s=[];                               // Initialise an empty stack.
                                    // O(1)
C .map(x=>x.split(' '))             // Split each line into an array of the two values.
                                    // O(1) per line = O(N) total.
  .map(x=>[w=x[1]*1,x[i=0]*1+w])    // Re-map the split values to an array storing the
                                    // radius and the right boundary.
                                    // O(1) per line = O(N) total.

  .sort((a,b)=>(c=a[1]-2*a[0])==(d=b[1]-2*b[0])?b[0]-a[0]:c-d)
                                    // Sort the circles on increasing left boundary and
                                    // then descending radius.
                                    // O(1) per comparison = O(N.log(N)) total.
  .map(x=>{                         // Loop through each circle
    while(i&&s[i-1][1]<x[1])        // Check to see if the current circle  is to the right
                                    // of the circles on the stack;
      N+=s[--i][0]?0:1;             // if so, decrement the length of the stack and if the
                                    // circle that pops off has radius equal to the total
                                    // radii of its children then increment the number of
                                    // regions by 1.

                                    // Since there can be at most N items on the stack then
                                    // there can be at most N items popped off the stack
                                    // over all the iterations; therefore this operation
                                    // has an O(N) total cost.
    i&&(s[i-1][0]-=x[0]);           // If there is a circle on the stack then this circle
                                    // is its child. Decrement the parent's radius by the
                                    // current child's radius.
                                    // O(1) per iteration
    s[i++]=x                        // Add the current circle to the stack.
  });
while(i)N+=s[--i][0]?0:1            // Finally, remove all the remaining circles from the
                                    // stack and if the circle that pops off has radius
                                    // equal to the total radii of its children then
                                    // increment the number of regions by 1.
                                    // Since there will always be at least one circle on the
                                    // stack then this has the added bonus of being the final
                                    // command so the value of N is printed to the console.
                                    // As per the previous comment on the complexity, there
                                    // can be at most N items on the stack so between this
                                    // and the iterations over the circles then there can only
                                    // be N items popped off the stack so the complexity of
                                    // all these tests on the circles on the stack is O(N).

The total time complexity is O(N) for everything except the sort which is O(N.log(N)) - however replacing this with a bucket sort, this will reduce the total complexity to O(N).

The memory required is O(N).

Guess what is next on my todo list... bucket sort in less than 150 characters. Done

MT0

Posted 2014-03-26T20:04:34.543

Reputation: 3 373

Bucket sort has only average complexity O(n) (in fact O(n+k)), but O(n^2) or O(n log n) worst case depending on the sorting algorithm used within buckets, so you'd need to do it in 50 characters. – Martin Ender – 2014-03-29T10:32:49.113

@m.buettner - Bucket sort can be done in O(N) worst-case complexity. It relies on careful choice of the buckets and an O(1) algorithm to assign to buckets. I've done it before (and proved it) and I just need to transfer the code to JavaScript. The algorithm above is already O(N.log(N)) so the only improvement is to get an O(N) sorting algorithm. – MT0 – 2014-03-29T10:43:48.720

I'd be interested in that proof and corresponding choice of buckets. :) – Martin Ender – 2014-03-29T10:44:49.073

http://www.cs.princeton.edu/~dpd/Papers/SCG-09-invited/Planarity%20testing.pdf (on page 556) gives an example of an O(N) bucket sort. https://archive.org/stream/PlanarityTestingByPathAddition/PlanarityTestingByPathAddition-MartynGTaylor-2012-05-30#page/n95/mode/2up (page 75) gives an example of a O(N) combined DFS search and bucket sort (time complexity is discussed on page 76). – MT0 – 2014-03-29T14:55:10.173

1@Mt0 hold on there, you might to read my addition to the complexity section of the question. With unbounded numbers sorting in linear time is most definitely impossible – Niklas B. – 2014-03-29T15:18:38.163

The construction in those papers work because you only have O (|V|) different values – Niklas B. – 2014-03-29T15:21:03.090

Passes all my testcases as well :) – Niklas B. – 2014-03-29T15:44:51.423

There's no such thing as a "hashmap (associative array) with a built in ordering" that still supports O(1) expected time operations (let alone deterministic). Your first version gets a higher score and is correct, so I would suggest you to restore it – Niklas B. – 2014-04-01T17:52:01.577

Also your updated version throws an error on some larger test cases and returns the wrong answer on some others. You can find the test data at the contest homepage. It's the task "KRUZNICE" from Contest #6. – Niklas B. – 2014-04-01T17:55:26.820

I just checked and it seems that Javascript does not have any guarantee that numbers as properties are automatically sorted: In Chrome, var x ={"111111111111111111":5, "11111112222222":6}; x outputs Object {111111111111111111: 5, 11111112222222: 6}. Is there something about that in the ES6 standard that will make me eat my own words? I still think that a data structure that does this would not qualify as a hash table, which naturally has no order, so it's not really relevant anyway – Niklas B. – 2014-04-01T18:14:14.577

Firstly, x={}; defines an object not an array. If you want an array then you need to do something like this x=[];x[11111]=1;x[1122]=2;x.forEach(x.forEach(function(a,i){console.log(a,i)}) which will show it as ordered in both Chrome and FireFox. Secondly, Chrome does not support ES6 - FireFox is the only browser that does (at the moment). – MT0 – 2014-04-01T21:27:57.543

Or rather: x=[];x[11111]=1;x[1122]=2;x.forEach(function(a,i){console.log(a,i)}) – MT0 – 2014-04-01T21:37:46.180

KRUZNICE 2, 3 & 4 all give the correct answer in FireFox - the others seem to be too big to paste the input string into the console and just hang FireFox on my machine. However, the two versions are identical after sorting the array (and the sorting is done using different methods but is functionally identical). – MT0 – 2014-04-01T22:19:46.917

@MT0 Unfortunately the code fails test cases 5, 6, 8 and 9. I used SpiderMonkey 24 to test it, which I think is the current Firefox JS engine. – Niklas B. – 2014-04-01T23:14:32.817

Now that I realize the confusion about arrays vs. objects, I also realize why your code seemed to be so slow in my tests. You in fact use an array, not an object. Try executing x=[];x[1000000000]=1;x.forEach(function(x) { print(x); }); Your code is in fact Ω(M) where M is the maximum input number, so it is not even polynomial in n and gets a +200 penalty... As I wrote in the question: "you can not assume that input numbers are bounded by a constant (although they are for practical reasons)" I would suggest you to go with the original version – Niklas B. – 2014-04-01T23:15:19.443

And if arrays were in fact implemented as sparse arrays, then you could either have O(1) expected access time or ordering, but not both – Niklas B. – 2014-04-01T23:32:22.180

Try using .map() (as I do in my code) rather than .forEach() - there is a huge difference in performance as .forEach() is the same as for(i=0;i<x.length;i++) whereas .map() will only run on the elements which are there - which is why it is not O(m). (Also note that SpiderMonkey is only released up to version 24 but FireFox is version 27) – MT0 – 2014-04-02T00:08:32.927

I am not aware of a way to test on version 27, so please feel free to show me how to do that. I can't imagine it makes a difference though. Now assuming the best case for your cause, that arrays are indeed implemented as sparse arrays, as I said, you cannot have both. Either you have ordering or you have O(1) expected time for the operations. In any case, you would not achieve the same score as your original attempt, so 155 is the score I give to this answer for now. I don't know where you got the -250 from, that would mean Javascript can solve element distinctness in O(n), which is impossible – Niklas B. – 2014-04-02T04:38:44.043

@MT0 Also, have you tried what happens if you do var x = []; x[1000000000] = 4; var N = 0; x.map(function(a) { N += a; }); N;? It just leaves N = 0, because map returns a lazy array and does not execute the function at all. That happens if x is beyond a certain size (there seems to be a threshold) and I guess that is why your script only works for small inputs. – Niklas B. – 2014-04-02T04:56:18.860