← Home
```go
package main

import (
	"bufio"
	"container/heap"
	"fmt"
	"math"
	"math/rand"
	"os"
	"sort"
	"strconv"
)

type Point struct {
	id   int
	x, y int64
}

type Segment struct {
	id     int
	P1, P2 Point
	dx, dy int64
}

type NodeData struct {
	id   int
	x, y float64
}

type Node struct {
	seg      Segment
	priority int
	left     *Node
	right    *Node
}

func evalAt(S Segment, X int64) (num, den int64) {
	num = S.P1.y*S.dx + (X-S.P1.x)*S.dy
	den = S.dx
	return
}

func isAbove(A, B Segment, X int64) bool {
	numA, denA := evalAt(A, X)
	numB, denB := evalAt(B, X)
	diff := numA*denB - numB*denA
	if diff != 0 {
		return diff > 0
	}
	slopeDiff := A.dy*B.dx - B.dy*A.dx
	return slopeDiff > 0
}

func split(node *Node, seg Segment, X int64) (*Node, *Node) {
	if node == nil {
		return nil, nil
	}
	if isAbove(seg, node.seg, X) {
		l, r := split(node.right, seg, X)
		node.right = l
		return node, r
	}
	l, r := split(node.left, seg, X)
	node.left = r
	return l, node
}

func merge(l, r *Node) *Node {
	if l == nil {
		return r
	}
	if r == nil {
		return l
	}
	if l.priority > r.priority {
		l.right = merge(l.right, r)
		return l
	}
	r.left = merge(l, r.left)
	return r
}

func insertNode(node *Node, newNode *Node, X int64) *Node {
	if node == nil {
		return newNode
	}
	if newNode.priority > node.priority {
		l, r := split(node, newNode.seg, X)
		newNode.left = l
		newNode.right = r
		return newNode
	}
	if isAbove(newNode.seg, node.seg, X) {
		node.right = insertNode(node.right, newNode, X)
	} else {
		node.left = insertNode(node.left, newNode, X)
	}
	return node
}

func removeNode(node *Node, seg Segment, X int64) *Node {
	if node == nil {
		return nil
	}
	if node.seg.id == seg.id {
		return merge(node.left, node.right)
	}
	if isAbove(seg, node.seg, X) {
		node.right = removeNode(node.right, seg, X)
	} else {
		node.left = removeNode(node.left, seg, X)
	}
	return node
}

func findAbove(root *Node, X int64, Y int64) *Segment {
	var best *Segment
	curr := root
	for curr != nil {
		num, den := evalAt(curr.seg, X)
		if Y*den < num {
			best = &curr.seg
			curr = curr.left
		} else {
			curr = curr.right
		}
	}
	return best
}

func findBelow(root *Node, X int64, Y int64) *Segment {
	var best *Segment
	curr := root
	for curr != nil {
		num, den := evalAt(curr.seg, X)
		if Y*den > num {
			best = &curr.seg
			curr = curr.right
		} else {
			curr = curr.left
		}
	}
	return best
}

func isInside(A, B, D Point) bool {
	cross := A.x*B.y - A.y*B.x
	if cross > 0 {
		return (A.x*D.y-A.y*D.x) > 0 && (D.x*B.y-D.y*B.x) > 0
	} else if cross < 0 {
		return (A.x*D.y-A.y*D.x) > 0 || (D.x*B.y-D.y*B.x) > 0
	}
	return (-B.x*D.y + B.y*D.x) > 0
}

type EventGroup struct {
	ending   []Segment
	starting []Segment
	verts    []int
}

type TempEdge struct {
	u, v     int
	w        float64
	directed bool
}

type Edge struct {
	to int
	w  float64
}

type Item struct {
	id    int
	dist  float64
	index int
}

type PriorityQueue []*Item

func (pq PriorityQueue) Len() int           { return len(pq) }
func (pq PriorityQueue) Less(i, j int) bool { return pq[i].dist < pq[j].dist }
func (pq PriorityQueue) Swap(i, j int) {
	pq[i], pq[j] = pq[j], pq[i]
	pq[i].index = i
	pq[j].index = j
}
func (pq *PriorityQueue) Push(x interface{}) {
	n := len(*pq)
	item := x.(*Item)
	item.index = n
	*pq = append(*pq, item)
}
func (pq *PriorityQueue) Pop() interface{} {
	old := *pq
	n := len(old)
	item := old[n-1]
	item.index = -1
	*pq = old[0 : n-1]
	return item
}

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	buf := make([]byte, 0, 10*1024*1024)
	scanner.Buffer(buf, 10*1024*1024)
	scanner.Split(bufio.ScanWords)

	nextInt := func() int {
		scanner.Scan()
		res, _ := strconv.Atoi(scanner.Text())
		return res
	}

	if !scanner.Scan() {
		return
	}
	N, _ := strconv.Atoi(scanner.Text())

	V := make([]Point, N)
	for i := 0; i < N; i++ {
		V[i] = Point{id: i, x: int64(nextInt()), y: int64(nextInt())}
	}
	s := nextInt() - 1
	t := nextInt() - 1

	segs := make([]Segment, N)
	for i := 0; i < N; i++ {
		p1, p2 := V[i], V[(i+1)%N]
		if p1.x > p2.x {
			p1, p2 = p2, p1
		}
		if p1.x < p2.x {
			segs[i] = Segment{id: i, P1: p1, P2: p2, dx: p2.x - p1.x, dy: p2.y - p1.y}
		} else {
			segs[i] = Segment{id: i, P1: p1, P2: p2, dx: 0, dy: p2.y - p1.y}
		}
	}

	uniqueX := make([]int64, 0)
	xMap := make(map[int64]bool)
	events := make(map[int64]*EventGroup)

	for i := 0; i < N; i++ {
		x := V[i].x
		if !xMap[x] {
			xMap[x] = true
			uniqueX = append(uniqueX, x)
			events[x] = &EventGroup{}
		}
		events[x].verts = append(events[x].verts, i)
		seg := segs[i]
		if seg.dx > 0 {
			events[seg.P1.x].starting = append(events[seg.P1.x].starting, seg)
			events[seg.P2.x].ending = append(events[seg.P2.x].ending, seg)
		}
	}
	sort.Slice(uniqueX, func(i, j int) bool { return uniqueX[i] < uniqueX[j] })

	var root *Node
	nextID := N
	nodes := make([]NodeData, 0, 3*N)
	for i := 0; i < N; i++ {
		nodes = append(nodes, NodeData{id: i, x: float64(V[i].x), y: float64(V[i].y)})
	}
	segmentPoints := make([][]int, N)
	for i := 0; i < N; i++ {
		segmentPoints[i] = append(segmentPoints[i], i, (i+1)%N)
	}

	var allEdges []TempEdge
	addEdge := func(u, v int, w float64, directed bool) {
		allEdges = append(allEdges, TempEdge{u, v, w, directed})
	}

	for _, X := range uniqueX {
		eg := events[X]
		for _, seg := range eg.ending {
			root = removeNode(root, seg, X)
		}

		sort.Slice(eg.verts, func(a, b int) bool {
			return V[eg.verts[a]].y < V[eg.verts[b]].y
		})
		vertsAtX := make([]Point, len(eg.verts))
		for idx, vID := range eg.verts {
			vertsAtX[idx] = Point{id: vID, x: V[vID].x, y: V[vID].y}
		}

		for idx, vID := range eg.verts {
			vPt := V[vID]
			A := Point{x: V[(vID+1)%N].x - vPt.x, y: V[(vID+1)%N].y - vPt.y}
			B := Point{x: V[(vID-1+N)%N].x - vPt.x, y: V[(vID-1+N)%N].y - vPt.y}

			if isInside(A, B, Point{x: 0, y: 1}) {
				cand1Valid := idx+1 < len(vertsAtX)
				cand2 := findAbove(root, X, vPt.y)

				if cand1Valid && cand2 != nil {
					num, den := evalAt(*cand2, X)
					if vertsAtX[idx+1].y*den < num {
						addEdge(vertsAtX[idx+1].id, vPt.id, float64(vertsAtX[idx+1].y-vPt.y), true)
					} else {
						hitY := float64(num) / float64(den)
						pID := nextID
						nextID++
						nodes = append(nodes, NodeData{pID, float64(X), hitY})
						segmentPoints[cand2.id] = append(segmentPoints[cand2.id], pID)
						addEdge(pID, vPt.id, hitY-float64(vPt.y), true)
					}
				} else if cand1Valid {
					addEdge(vertsAtX[idx+1].id, vPt.id, float64(vertsAtX[idx+1].y-vPt.y), true)
				} else if cand2 != nil {
					num, den := evalAt(*cand2, X)
					hitY := float64(num) / float64(den)
					pID := nextID
					nextID++
					nodes = append(nodes, NodeData{pID, float64(X), hitY})
					segmentPoints[cand2.id] = append(segmentPoints[cand2.id], pID)
					addEdge(pID, vPt.id, hitY-float64(vPt.y), true)
				}
			}

			if isInside(A, B, Point{x: 0, y: -1}) {
				cand1Valid := idx-1 >= 0
				cand2 := findBelow(root, X, vPt.y)

				if cand1Valid && cand2 != nil {
					num, den := evalAt(*cand2, X)
					if vertsAtX[idx-1].y*den > num {
						addEdge(vPt.id, vertsAtX[idx-1].id, float64(vPt.y-vertsAtX[idx-1].y), true)
					} else {
						hitY := float64(num) / float64(den)
						pID := nextID
						nextID++
						nodes = append(nodes, NodeData{pID, float64(X), hitY})
						segmentPoints[cand2.id] = append(segmentPoints[cand2.id], pID)
						addEdge(vPt.id, pID, float64(vPt.y)-hitY, true)
					}
				} else if cand1Valid {
					addEdge(vPt.id, vertsAtX[idx-1].id, float64(vPt.y-vertsAtX[idx-1].y), true)
				} else if cand2 != nil {
					num, den := evalAt(*cand2, X)
					hitY := float64(num) / float64(den)
					pID := nextID
					nextID++
					nodes = append(nodes, NodeData{pID, float64(X), hitY})
					segmentPoints[cand2.id] = append(segmentPoints[cand2.id], pID)
					addEdge(vPt.id, pID, float64(vPt.y)-hitY, true)
				}
			}
		}

		for _, seg := range eg.starting {
			newNode := &Node{seg: seg, priority: rand.Int()}
			root = insertNode(root, newNode, X)
		}
	}

	for i := 0; i < N; i++ {
		pts := segmentPoints[i]
		sort.Slice(pts, func(a, b int) bool {
			na, nb := nodes[pts[a]], nodes[pts[b]]
			if math.Abs(na.x-nb.x) > 1e-9 {
				return na.x < nb.x
			}
			return na.y < nb.y
		})

		for j := 0; j < len(pts)-1; j++ {
			u, v := pts[j], pts[j+1]
			nu, nv := nodes[u], nodes[v]
			dx := nu.x - nv.x
			dy := nu.y - nv.y
			dist := math.Sqrt(dx*dx + dy*dy)
			addEdge(u, v, dist, false)
		}
	}

	adj := make([][]Edge, nextID)
	for _, e := range allEdges {
		adj[e.u] = append(adj[e.u], Edge{e.v, e.w})
		if !e.directed {
			adj[e.v] = append(adj[e.v], Edge{e.u, e.w})
		}
	}

	dist := make([]float64, nextID)
	for i := range dist {
		dist[i] = math.Inf(1)
	}
	dist[s] = 0

	pq := make(PriorityQueue, 0)
	heap.Push(&pq, &Item{id: s, dist: 0})

	for pq.Len() > 0 {
		curr := heap.Pop(&pq).(*Item)
		u := curr.id
		if curr.dist > dist[u] {
			continue
		}
		if u == t {
			break
		}
		for _, edge := range adj[u] {
			if dist[u]+edge.w < dist[edge.to] {
				dist[edge.to] = dist[u] + edge.w
				heap.Push(&pq, &Item{id: edge.to, dist: dist[edge.to]})
			}
		}
	}

	fmt.Printf("%.9f\n", dist[t])
}
```