Problem:
JOI 国のお祭り事情(Festivals in JOI Kingdom)

JOI 国にはN 個の街があり，それらの間はM 本の双方向に通行可能な道路で結ばれている．国民は道路を通ってそれらの街を移動する．

JOI 国の国民には，お祭りが好きな人が多く，現在，K 個の街でお祭りが開催されており，非常に賑わっている．一方で，一部の国民はお祭りを騒がしいとして嫌い，お祭りにできるだけ近づきたくないと思っている．

そこで国王は，優秀なプログラマーであるあなたに，そのようなお祭りを嫌う国民のため，ある街からある街に移動するために，お祭りが開催されている街にどれだけ近づかずに移動することができるかを高速に調べることのできるプログラムの作成を依頼した．

課題

道路の情報とお祭りが開催されている街の情報，およびQ 個のクエリ（出発する街Si と行きたい街Tiの組）が与えられる．各クエリi に対し，街Si から街Ti へのすべての経路のうち，お祭りまでの距離が最大となる経路の，お祭りまでの距離を求めるプログラムを作成せよ．ただし，ある経路のお祭りまでの距離とは，経路上の街からお祭りが開催されている街までの移動距離の最小値のことである．

制限

2 ≤ N ≤ 100000 (= 105)     JOI 国の街の個数

1 ≤ M ≤ 200000 (= 2 × 105)     JOI 国の道路の本数

1 ≤ K ≤ N     お祭りが開催されている街の個数

1 ≤ Q ≤ 100000 (= 105)     クエリの個数

1 ≤ Li ≤ 1000     i 番目の道路の長さ

入力

標準入力から以下のデータを読み込め．

1 行目には整数N, M, K, Q が空白を区切りとして書かれている．N はJOI 国の街の個数を，M はJOI国の道路の本数を，K はお祭りが開催されている街の個数を，Q はクエリの個数をそれぞれ表す．街には1, 2, ... , N の番号がつけられている．

続くM 行は道路の情報を表す．i + 1 行目(1 ≤ i ≤ M) には整数Ai, Bi, Li (1 ≤ Ai ≤ N, 1 ≤ Bi ≤ N) が空白を区切りとして書かれている．これは，i 番目の道路が街Ai と街Bi を結んでおり，長さがLi であることを表す．道路の両端が同じ街であることはない．また，任意の2 つの街p, q に対し，p とqを結ぶ道路は2 本以上存在しない．どの街からどの街へもいくつかの道路をたどって行くことができる．

続くK 行はお祭りが開催されている街の情報を表す．i + M + 1 行目(1 ≤ i ≤ K) には1 つの整数Fi(1 ≤ Fi ≤ N) が書かれている．これは街Fi でお祭りが開催されていることを表す．F1, ..., FK の中に同じ値が2 回以上現れることはない．

続くQ 行はクエリを表す．i + M + K + 1 行目(1 ≤ i ≤ Q) には2 つの整数Si, Ti (1 ≤ Si ≤ N, 1 ≤ Ti ≤ N, Si ≠ Ti) が空白を区切りとして書かれている．これはi 番目のクエリの出発する街がSi であり行きたい街がTi であることを表す．

出力

標準出力に，全クエリへの答えをQ 行で出力せよ．すなわち，i 行目に，街Si から街Ti へのすべての経路のうち，お祭りまでの距離が最大となる経路の，お祭りまでの距離を表す整数を出力せよ．

採点基準

採点用データのうち，

配点の10% 分については，Q = 1 を満たす．

配点の20% 分については，N ≤ 5000, Q ≤ 5000 を満たす．

配点の30% 分については，これら2 つの条件の少なくとも一方を満たす．また，これら2 つの条件の両方を満たすような採点用データはない．

入出力例

入力例 1

6 6 2 3
1 2 5
2 3 4
2 4 6
3 5 9
4 5 3
5 6 7
1
6
3 4
5 2
1 4

出力例 1

7
5
0

6 つの街が6 本の道路で結ばれており，お祭りは街1, 6 の2 つの街で開催されている．クエリは以下の3 つである．

1 つ目のクエリは街3 から街4 へ行くというものである．街2 を経由する経路ではお祭りまでの距離は5，街5 を経由する経路ではお祭りまでの距離は7 となるため，答えは7 である．

2 つ目のクエリは街5 から街2 へ行くというものである．街3 と街4 のどちらを経由しても，街2 でお祭りまでの距離が最小となり，答えは5 である．

3 つ目のクエリは街1 から街4 へ行くというものである．街1 はお祭りが開催されている街であるため，答えは0 となる．

図1: 入力例1

入力例 2

12 17 2 5
1 3 6
1 6 7
2 3 8
2 4 4
2 8 11
2 12 2
3 6 3
3 7 8
3 11 2
4 12 2
5 10 3
6 10 5
8 9 6
8 12 7
9 10 6
11 9 10
12 9 5
8
7
2 6
5 2
1 10
8 9
9 4

出力例 2

8
8
11
0
6

図1: 入力例2

問題文と自動審判に使われるデータは、情報オリンピック日本委員会が作成し公開している問題文と採点用テストデータです。

Solution in Scala (judge verdict: Accepted)

//https://onlinejudge.u-aizu.ac.jp/problems/0575
object Main extends App {
  import scala.io.StdIn._
  solve
  def solve: Unit = {
    val Array(n, m, k, q) = readLine.trim.split(' ').map(_.toInt)
    val towns = Array.tabulate(n){_ ⇒ new Town}
    for (_ ← 0 until m){
      val Array(a, b, l) = readLine.trim.split(' ').map(_.toInt)
      towns(a - 1).addPathTo(b - 1, l)
      towns(b - 1).addPathTo(a - 1, l)
    }
    val minDistance = Array.tabulate(n){_ ⇒ Int.MaxValue}
    val queue = new PriorityQueue[(Int, Int)](n)(Ordering.by[(Int, Int), Int]{case (_, a) ⇒ a})
    val keySpecifier = Array.tabulate(n){i ⇒ queue.enqueue((i, Int.MaxValue))}
    for (_ ← 0 until k){
      val f = readLine.trim.toInt
      minDistance(f - 1) = 0
      queue.keyChange(keySpecifier(f - 1), (f - 1, 0))
    }
    while (queue.nonEmpty){
      val (town, distance) = queue.dequeue
      if (minDistance(town) == distance){
        for (Path(to, len) ← towns(town).path){
          if (minDistance(to) > distance + len) {
            minDistance(to) = distance + len
            queue.keyChange(keySpecifier(to), (to, distance + len))
          }
        }
      }
    }
    for (i ← towns.indices.sortBy(minDistance)(Ordering[Int].reverse)){
      for (Path(to, _) ← towns(i).path){
        if (minDistance(i) <= minDistance(to)) {
          towns(i).mergeTree(towns(to), minDistance(i))
        }
      }
    }
    for (_ ← 0 until q){
      val Array(s, t) = readLine.trim.split(' ').map(_.toInt)
      println(
        towns(s - 1).minDistance(towns(t - 1))
      )
    }
  }
  def lowerBound(min: Int, max: Int)(func: Int ⇒ Boolean): Int = {
    if (min >= max) min
    else {
      val mid = max - (max - min) / 2 - (max - min) % 2
      if (func(mid)){
        lowerBound(min, mid)(func)
      }else {
        lowerBound(mid + 1, max)(func)
      }
    }
  }
  case class ValueWithTime[T](value: T, time: Int)
  class LoggedUnionFindTree {
    private var mParent: Option[ValueWithTime[LoggedUnionFindTree]] = None
    private var mRoot: Option[LoggedUnionFindTree] = None
    private var mSize: Int = 1
    private def parentAt(time: Int): LoggedUnionFindTree = {
      mParent match{
        case Some(p) if p.time <= time ⇒ {
          var suspect = p.value
          while (suspect.mParent.isDefined && suspect.mParent.get.time <= time){
            suspect = suspect.mParent.get.value
          }
          suspect
        }
        case _ ⇒ this
      }
    }
    private def root: LoggedUnionFindTree = {
      mRoot = mRoot.map(_.root)
      mRoot match{
        case Some(r) ⇒ r
        case None ⇒ this
      }
    }
    private def path(res: List[LoggedUnionFindTree]): List[LoggedUnionFindTree] = {
      mParent.map(_.value.path(this::res)) match{
        case Some(p) ⇒ p
        case None ⇒ this::res
      }
    }
    private def path: List[LoggedUnionFindTree] = {
      path(Nil)
    }
    def isSameUnionAt(that: LoggedUnionFindTree, time: Int): Boolean = {
      parentAt(time) == that.parentAt(time)
    }
    def merge(that: LoggedUnionFindTree, time: Int): Unit = {
      val thisRoot = root
      val thatRoot = that.root
      if (thisRoot != thatRoot) {
        if (thisRoot.mSize > thatRoot.mSize) {
          thatRoot.mParent = Some(ValueWithTime(thisRoot, time))
          thatRoot.mRoot = Some(thisRoot)
          thisRoot.mSize += thatRoot.mSize
        } else {
          thisRoot.mParent = Some(ValueWithTime(thatRoot, time))
          thisRoot.mRoot = Some(thatRoot)
          thatRoot.mSize += thisRoot.mSize
        }
      }
    }
    def mergedTime(that: LoggedUnionFindTree): Int = {
      var thisPath = path
      var thatPath = that.path
      while (thisPath.nonEmpty && thatPath.nonEmpty && thisPath.head == thatPath.head){
        thisPath = thisPath.tail
        thatPath = thatPath.tail
      }
      if (thisPath.isEmpty) thatPath.head.mParent.get.time
      else if (thatPath.isEmpty) thisPath.head.mParent.get.time
      else if (thisPath.head.mParent.get.time < thatPath.head.mParent.get.time) thatPath.head.mParent.get.time
      else thisPath.head.mParent.get.time
    }
  }
  object PriorityQueue {
    class KeySpecifier private[PriorityQueue] (private[PriorityQueue] var index: Int) {
      def this(){this(-1)}
      private[PriorityQueue] var mIsValid: Boolean = true
    }
  }
  class PriorityQueue[K](private val maxSize: Int)(implicit val ordering: Ordering[K]) {
    private val mIndexHolder: Array[PriorityQueue.KeySpecifier] = Array.tabulate(maxSize){i ⇒ new PriorityQueue.KeySpecifier(i)}
    private var mSize: Int = 0
    private val mElements: Array[Option[K]] = Array.tabulate(maxSize){_ ⇒ None}
    def keyChange(keySpecifier: PriorityQueue.KeySpecifier, newKey:K): Unit = {
      if (!keySpecifier.mIsValid) throw new Exception
      if (ordering.compare(mElements(keySpecifier.index).get, newKey) > 0){
        mElements(keySpecifier.index) = Some(newKey)
        keyDecrease(keySpecifier.index)
      }else {
        mElements(keySpecifier.index) = Some(newKey)
        keyIncrease(keySpecifier.index)
      }
    }
    def enqueue(element: K): PriorityQueue.KeySpecifier = {
      mElements(mSize) = Some(element)
      mIndexHolder(mSize) = new PriorityQueue.KeySpecifier(mSize)
      val res = mIndexHolder(mSize)
      mSize += 1
      keyDecrease(mSize - 1)
      res
    }
    def dequeue: K = {
      val res = mElements(0).get
      mIndexHolder(0).mIsValid = false
      mElements(0) = None
      mSize -= 1
      swap(0, mSize)
      keyIncrease(0)
      res
    }
    def size:Int = mSize
    def isEmpty: Boolean = mSize == 0
    def nonEmpty: Boolean = mSize != 0
    private def swap(a: Int, b: Int): Unit = {
      val temp = mElements(a)
      mElements(a) = mElements(b)
      mElements(b) = temp
      val idx = mIndexHolder(a)
      mIndexHolder(a) = mIndexHolder(b)
      mIndexHolder(b) = idx
      mIndexHolder(a).index = a
      mIndexHolder(b).index = b
    }
    private def keyIncrease(index: Int): Unit = {
      if ((index + 1) * 2 < mSize){
        val min = if (ordering.compare(mElements((index + 1) * 2).get, mElements((index + 1) * 2 - 1).get) > 0) (index + 1) * 2 - 1 else (index + 1) * 2
        if (ordering.compare(mElements(index).get, mElements(min).get) > 0) {
          swap(min, index)
          keyIncrease(min)
        }
      }else if ((index + 1) * 2 - 1< mSize){
        if (ordering.compare(mElements(index).get, mElements((index + 1) * 2 - 1).get) > 0){
          swap(index, (index + 1) * 2 - 1)
          keyIncrease((index + 1) * 2 - 1)
        }
      }
    }
    private def keyDecrease(index: Int): Unit = {
      if (index != 0 && ordering.compare(mElements((index - 1) / 2).get, mElements(index).get) > 0){
        swap(index, (index - 1) / 2)
        keyDecrease((index - 1) / 2)
      }
    }
  }
  class Town{
    private var mPath: List[Path] = Nil
    private val mUnion: LoggedUnionFindTree = new LoggedUnionFindTree
    def addPathTo(to: Int, distance: Int): Unit = {
      mPath ::= Path(to, distance)
    }
    def mergeTree(that: Town, minDistance: Int): Unit = {
      mUnion.merge(that.mUnion, -minDistance)
    }
    def -(to: Town, minDistance: Int): Boolean = mUnion.isSameUnionAt(to.mUnion, -minDistance)
    def path = mPath
    def minDistance(that: Town): Int = {
      -mUnion.mergedTime(that.mUnion)
    }
  }
  case class Path(to: Int, distance: Int)
  implicit class ScopeFunc[T](private val value: T) {
    def let[A](func:T ⇒ A): A = func(value)
    def also(func:T ⇒ Unit): T = {
      func(value)
      value
    }
  }
}