Skip to content

Commit

Permalink
Day24
Browse files Browse the repository at this point in the history
  • Loading branch information
elizarov committed Dec 24, 2023
1 parent 6e19935 commit 9a4dd8e
Show file tree
Hide file tree
Showing 3 changed files with 245 additions and 0 deletions.
76 changes: 76 additions & 0 deletions src/Day24_1.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import java.math.BigDecimal

fun main() {
val input = readInput("Day24")

data class V3(val x: Long, val y: Long, val z: Long) {
override fun toString(): String = "$x, $y, $z"
}
data class Stone(val p: V3, val v: V3) {
override fun toString(): String = "$p @ $v"
}

fun String.toV3(): V3 = split(",")
.map { it.trim().toLong() }
.let { (x, y, z) -> V3(x, y, z) }

val stones = input.map { s ->
val (p, v) = s.split("@")
Stone(p.toV3(), v.toV3())
}
// println(stones.size)

data class Line(val a: BigDecimal, val b: BigDecimal, val c: BigDecimal)

fun Stone.toLine(): Line {
val a = v.y.toBigDecimal()
val b = (-v.x).toBigDecimal()
val c = a * p.x.toBigDecimal() + b * p.y.toBigDecimal()
return Line(a, b, c)
}

fun det(a: BigDecimal, b: BigDecimal, c: BigDecimal, d: BigDecimal): BigDecimal = a * d - b * c

// val rMin = 7.toBigDecimal()
// val rMax = 27.toBigDecimal()
val rMin = 200000000000000.toBigDecimal()
val rMax = 400000000000000.toBigDecimal()

// n / d in rMin..rMax
fun inRange(n: BigDecimal, d: BigDecimal): Boolean = when {
d > BigDecimal.ZERO -> n in (rMin * d)..(rMax * d)
d < BigDecimal.ZERO -> n in (rMax * d)..(rMin * d)
else -> error("!!!")
}

fun isFuture(p: Long, v: Long, n: BigDecimal, d: BigDecimal): Boolean = when {
v > 0 -> if (d > BigDecimal.ZERO) n > d * p.toBigDecimal() else n < d * p.toBigDecimal()
v < 0 -> if (d > BigDecimal.ZERO) n < d * p.toBigDecimal() else n > d * p.toBigDecimal()
else -> error("!!!")
}

var cnt = 0
for (i in stones.indices) for (j in i + 1..<stones.size) {
val si = stones[i]
val li = si.toLine()
val sj = stones[j]
val lj = sj.toLine()
val d = det(li.a, li.b, lj.a, lj.b)
if (d == BigDecimal.ZERO) {
if (li.a * sj.p.x.toBigDecimal() + li.b * sj.p.y.toBigDecimal() == li.c) {
error(" !!! same")
}
continue
}
val nx = det(li.c, li.b, lj.c, lj.b) // x = nx / d
val ny = det(li.a, li.c, lj.a, lj.c) // y = ny / d
if (!inRange(nx, d) || !inRange(ny, d)) continue
if (!isFuture(si.p.x, si.v.x, nx, d) || !isFuture(si.p.y, si.v.y, ny, d)) continue
if (!isFuture(sj.p.x, sj.v.x, nx, d) || !isFuture(sj.p.y, sj.v.y, ny, d)) continue
// println("intersection: ${nx.toDouble() / d.toDouble()}, ${ny.toDouble() / d.toDouble()}")
// println(" A: $si")
// println(" B: $sj")
cnt++
}
println(cnt)
}
62 changes: 62 additions & 0 deletions src/Day24_1_doubles.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
fun main() {
val input = readInput("Day24")

data class V3(val x: Long, val y: Long, val z: Long) {
override fun toString(): String = "$x, $y, $z"
}
data class Stone(val p: V3, val v: V3) {
override fun toString(): String = "$p @ $v"
}

fun String.toV3(): V3 = split(",")
.map { it.trim().toLong() }
.let { (x, y, z) -> V3(x, y, z) }

val stones = input.map { s ->
val (p, v) = s.split("@")
Stone(p.toV3(), v.toV3())
}
// println(stones.size)

data class Line(val a: Long, val b: Long, val c: Long)

fun Stone.toLine(): Line {
val a = v.y
val b = -v.x
val c = Math.multiplyExact(a, p.x) + Math.multiplyExact(b, p.y)
return Line(a, b, c)
}

fun det(a: Long, b: Long, c: Long, d: Long): Double = a.toDouble() * d - b.toDouble() * c.toDouble()

// val rMin = 7
// val rMax = 27
val rMin = 200000000000000
val rMax = 400000000000000

// n / d in rMin..rMax
fun inRange(t: Double): Boolean = t >= rMin && t <= rMax

fun isFuture(p: Long, v: Long, t: Double): Boolean = when {
v > 0 -> t >= p
v < 0 -> t <= p
else -> error("!!!")
}

var cnt = 0
for (i in stones.indices) for (j in i + 1..<stones.size) {
val si = stones[i]
val li = si.toLine()
val sj = stones[j]
val lj = sj.toLine()
val d = det(li.a, li.b, lj.a, lj.b)
if (d == 0.0) continue
val x = det(li.c, li.b, lj.c, lj.b) / d
val y = det(li.a, li.c, lj.a, lj.c) / d
if (!inRange(x) || !inRange(y)) continue
if (!isFuture(si.p.x, si.v.x, x) || !isFuture(si.p.y, si.v.y, y)) continue
if (!isFuture(sj.p.x, sj.v.x, x) || !isFuture(sj.p.y, sj.v.y, y)) continue
cnt++
}
println(cnt)
}
107 changes: 107 additions & 0 deletions src/Day24_2.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import java.math.BigInteger
import kotlin.math.*

fun main() {
val input = readInput("Day24")

data class V3(val x: Long, val y: Long, val z: Long) {
override fun toString(): String = "$x, $y, $z"
}
data class Stone(val p: V3, val v: V3) {
override fun toString(): String = "$p @ $v"
}

fun String.toV3(): V3 = split(",")
.map { it.trim().toLong() }
.let { (x, y, z) -> V3(x, y, z) }

val stones = input.map { s ->
val (p, v) = s.split("@")
Stone(p.toV3(), v.toV3())
}

fun Long.divSurely(that: Long): Long {
check(this % that == 0L)
return this / that
}

data class Range(val pi: LongRange, val v: Long)

fun checkTimes(coord: String, ps: List<Long>, vs: List<Long>): Long {
val n = ps.size
check(vs.size == n)

val minV = -1000L
val maxV = 1000L
val minP = 0L
val maxP = 1_000_000_000_000_000L
check(minV < vs.min() && maxV > vs.max())
check(minP < ps.min() && maxP > ps.max())
val vss = vs.zip(ps).groupBy { it.first }.mapValues { e -> e.value.map { it.second }.toSet() }
val rs = ArrayList<Range>()

tailrec fun gcd(x: BigInteger, y: BigInteger): BigInteger = if (y == BigInteger.ZERO) x else gcd(y, x % y)
fun lcm(x: BigInteger, y: BigInteger) = x * y / gcd(x, y)
fun BigInteger.floorDiv(d: BigInteger): BigInteger =
if (this >= BigInteger.ZERO) divide(d) else -(-this + d - BigInteger.ONE).divide(d)

fun Long.floorDiv(d: BigInteger): BigInteger = toBigInteger().floorDiv(d)
fun Long.mod(d: BigInteger): BigInteger = toBigInteger().mod(d)
fun modRoundUp(x: Long, m: BigInteger, r: BigInteger): BigInteger =
(x.floorDiv(m) + if (x.mod(m) <= r) BigInteger.ZERO else BigInteger.ONE) * m + r

fun modRoundDn(x: Long, m: BigInteger, r: BigInteger): BigInteger =
(x.floorDiv(m) - if (x.mod(m) >= r) BigInteger.ZERO else BigInteger.ONE) * m + r

vloop@ for (v in minV..maxV) {
val p1 = vs.withIndex().filter { v < it.value }.maxOfOrNull { ps[it.index] } ?: minP
val p2 = vs.withIndex().filter { v > it.value }.minOfOrNull { ps[it.index] } ?: maxP
if (p1 > p2) continue
var pmod = BigInteger.ONE
var prem = BigInteger.ZERO
var p1r = p1
var p2r = p2
for (i in 0..<n) {
val pi = ps[i]
val vi = vs[i]
if (v == vi) {
val p0 = vss[v]?.singleOrNull() ?: continue@vloop
if (p0 !in p1r..p2r) continue@vloop
p1r = p0
p2r = p0
continue
}
// t_meet = (p - pi) / (vi - v)
val d = abs(vi - v).toBigInteger()
val r = pi.mod(d)
val pmod2 = lcm(pmod, d)
var prem2 = prem
while (prem2 < pmod2) {
if (prem2.remainder(d) == r) break
prem2 += pmod
if (prem2 >= pmod2) continue@vloop
if (prem2 > p2r.toBigInteger()) continue@vloop
}
pmod = pmod2
prem = prem2
val p1n = modRoundUp(p1r, pmod, prem)
val p2n = modRoundDn(p2r, pmod, prem)
if (p1n > p2n) continue@vloop
check(p1n >= p1r.toBigInteger())
check(p2n <= p2r.toBigInteger())
p1r = p1n.toLong()
p2r = p2n.toLong()
}
println("$coord -> $p1r .. $p2r, v = $v")
rs += Range(p1r..p2r, v)
}
println("$coord: ${rs.size} ranges")
return rs.single().pi.first
}

val x = checkTimes("x", stones.map { it.p.x }, stones.map { it.v.x })
val y = checkTimes("y", stones.map { it.p.y }, stones.map { it.v.y })
val z = checkTimes("z", stones.map { it.p.z }, stones.map { it.v.z })

println(x + y + z)
}

0 comments on commit 9a4dd8e

Please sign in to comment.