abc456_e

関連記事

1. 問題

E – Endless Holidays スライド AtCoder ABC 456 E問題の解説スライド。都市グラフ、週スケジュール表、問題の要点を視覚化。 E — Endless Holidays ABC 456 · 450点 · グラフ上の周期スケジューリング問題 都市グラフ(例: ケース1) 都市 1 都市 4 都市 2 都市 3 休日(曜1) 平日(曜1) 解の例 4→2→1→4→2→…(毎昼○) 週スケジュール(W=3) 曜日1 曜日2 曜日3 都市1 都市2 都市3 都市4 移動ルール 曜日1の昼: 好きな都市を選択 毎夜: 隣接都市へ移動 or 留まる 週の循環 曜日 1 → 2 → … → W → 1 → 2 →… 毎昼, いる都市が休日でなければ失敗 N, M ≤ 10⁵(連結グラフ) W ≤ 10 · T ≤ 10⁵ 問いと出力 永続移動は可能か? 毎日昼に休日の都市にいながら 無限に移動を続けられるか? (グラフは連結・週は循環) 出力 Yes … 永続移動が可能 No … 不可能 入出力例 ケース1 (4都市, W=3) → Yes(4→2→1→4→…) ケース2 (1都市, W=4) → Yes(留まり続ける) ケース3 (5都市, W=7) → No 実行時間: 3秒 / メモリ: 1024 MiB

NN 個の都市と MM 本の無向道路があり、各都市 ii には長さ WW の文字列 SiS_i で休日の曜日が与えられます。
高橋君は曜日 11 の昼に任意の都市から始め、毎晩「同じ都市に留まる」か「隣接都市へ移動」できます。
各日の昼にいる都市が、その曜日で休日になるように、無限に移動を続けられるか判定します。
TT 個のテストケースそれぞれについて、可能なら `Yes`、不可能なら `No` を出力します。

2. コード(WA11)

;; (make-edges 4 '(1 2 1 4 2 4 2 3))
;;=> #(NIL (4 2) (3 4 1) (2) (2 1))
(defun make-edges (N lst)
  (let* ((ary (make-array (1+ N) :element-type 'list
                            :initial-element nil)))
    (loop for (u v) on lst by #'cddr 
          while v
          do
             (push v (aref ary u))
             (push u (aref ary v)))
    ary))

;; (edges->nodes *e* 2) ;=> (3 4 1)
(defun edges->nodes (edges n)
  (aref edges n))

;; (make-holidays 4 3 '("xxo" "xox" "oxo" "oxx")) ;=>
;; #2A((NIL NIL NIL NIL)
;;     (NIL NIL NIL T)
;;     (NIL NIL T NIL)
;;     (NIL T NIL T)
;;     (NIL T NIL NIL))
(defun make-holidays (N W lines)
  (let* ((ary2d (make-array (list (1+ N) (1+ W))
                            :element-type 'boolean
                            :initial-element nil)))
    (loop for city from 1 to N
          for line in lines
          do (loop for day from 1 to W
                   for ch across line
                   do (setf (aref ary2d city day)
                            (eql ch #\o))))
    ary2d))


;; (defparameter *e* (make-edges 4 '(1 2 1 4 2 4 2 3)))
;; (defparameter *h*
;;   (make-holidays 4 3 '("xxo" "xox" "oxo" "oxx")))


(defun cycle (x cycle)
  (1+ (mod (1- x) cycle)))

;; (next-holiday-cities 3 2 3 *e* *h*) ;=> (3 1)
;; (next-holiday-cities 3 2 4 *e* *h*) ;=> (3 4)
;; (next-holiday-cities 3 1 2 *e* *h*) ;=> (2)
(defun next-holiday-cities (W current-city next-day edges holidays)
  (loop for city in (cons current-city
                          (edges->nodes edges current-city))
        when (aref holidays city (cycle next-day W))
          collect city))

;; (holiday-cities 4 3 1 *h*) ;=>  (3 4)
(defun holiday-cities (N W day holidays)
  (loop for city from 1 to N
        when (aref holidays city (cycle day W))
          collect city))

;; (next-cities 3 '(1 2 3 4) 2 *e* *h*) ;=> (2)
;; (next-cities 3 '(2) 3 *e* *h*)       ;=> (3 1)
;; (next-cities 3 '(3 1) 4 *e* *h*)     ;=> (4)
;; (next-cities 3 nil 4 *e* *h*)        ;=> NIL
(defun next-cities (W cities next-day edges holidays)
  (loop for city in cities
        append (next-holiday-cities
                W city next-day edges holidays) into result
        finally (return (remove-duplicates result))))

;; (endless-holiday-p 4 3 *e* *h*) ;=> T
(defun endless-holiday-p (N W edges holidays)
  (loop for day from 2 to (1+ W)
        with current-cities = (holiday-cities N W 1 holidays)
        while current-cities
        do (setf current-cities
                 (next-cities
                  W current-cities day edges holidays))
        finally (return (consp current-cities))))

(defun print-yesno (c)
  (declare (type boolean c))
  (princ (if c "Yes" "No")))

(defun main ()
  (let* ((times (read)))
    (loop repeat times
          for N = (read)
          for M = (read)
          for edge-list = (loop repeat (* M 2)
                                collect (read))
          for W = (read)
          for lines = (loop repeat N
                            collect (read-line))
          for edges = (make-edges N edge-list)
          for holidays = (make-holidays N W lines)
          do (print-yesno (endless-holiday-p
                           N W edges holidays))
             (terpri))))

#-swank(main)Code language: Lisp (lisp)
2. コード(WA11)

2.1. 開始時の都市に戻れないときがある?(TLE14)

一巡したときに、開始時の都市に戻れないと、最終的にどん詰まりになることがあることに気づきました。

(defun endless-holiday-p (N W edges holidays)
  (labels ((rec (start-cities)
             (loop for day from 2 to (1+ W)
                   with current-cities = start-cities
                   while current-cities
                   do (setf current-cities
                            (next-cities
                             W current-cities day edges holidays))
                   finally (return
                             (cond ((null current-cities) nil)
                                   ((equal start-cities current-cities) t)
                                   (t (rec current-cities)))))))
    (rec (holiday-cities N W 1 holidays))))Code language: JavaScript (javascript)

しかし、どうも振動してループしてしまうことがあるようです。

2.1. 開始時の都市に戻れないときがある?(TLE14)

2.2. ループをチェックした(TLE1)

そこで、スタート地点ごとに1週間後に移動可能な都市を求めて、それグラフにすることにしました。
そして、そのグラフ上にループ構造があれば、いつまでも休日を続けていけます。

;; (next-week-cities 4 3 *e* *h* 1) ;=> (3 4)
(defun next-week-cities (N W edges holidays city)
  (declare (ignore N))
  (loop for day from 2 to (1+ W)
        with current-cities = (list city)
        while current-cities
        do (setf current-cities
                 (next-cities
                  W current-cities day edges holidays))
        finally (return current-cities)))

;; (week-edges 4 3 *e* *h*) ;=> #(NIL NIL NIL (4 3) (4 3))
(defun week-edges (N W edges holidays)
  (let* ((start-cities (holiday-cities N W 1 holidays))
         (ary (make-array (1+ N) :element-type 'list
                                 :initial-element nil)))
    (loop for ca in start-cities
          do (loop for cb in (next-week-cities N W edges holidays ca)
                   do (push cb (aref ary ca)))
          finally (return ary))))
Code language: Lisp (lisp)

グラフにループ構造があるかは、DFSで途中の場所 :now を見つけたら判定できます。

;; (has-cycle-p 4 #(NIL NIL NIL (4 3) (4 3))) ;=> T
;; (has-cycle-p 4 #(NIL NIL NIL (4) (1))) ;=> NIL
(defun has-cycle-p (N edges)
  (let* ((state (make-array (1+ N) :initial-element :before)))
    (labels ((visit (v)
               (case (aref state v)
                 (:now t)
                 (:done nil)
                 (t (setf (aref state v) :now)
                  (let ((found-cycle (some #'visit (aref edges v))))
                    (setf (aref state v) :done)
                    found-cycle)))))
      (loop for i from 1 to N
            thereis (visit i)))))
Code language: PHP (php)

ループが見つからなかったノードは、:done にして再計算から除外します。

ここまですれば、判定そのものはシンプルです。

(defun endless-holiday-p/graph (N W edges holidays)
  (has-cycle-p N (week-edges N W edges holidays)))

結果は、TLE 1。
惜しいです。

2.2. ループをチェックした(TLE1)

一つだけ、異様にメモリを消費して、時間もオーバーしているので、何か見落としがあって処理がループしているかも。

;; (defparameter *e* (make-edges 4 '(1 2 1 4 2 4 2 3)))
;; (defparameter *h* (make-holidays 4 3 '("xxo" "xox" "oxo" "oxx")))

;; (make-edges 4 '(1 2 1 4 2 4 2 3))
;;=> #(NIL (4 2) (3 4 1) (2) (2 1))
(defun make-edges (N lst)
  (let* ((ary (make-array (1+ N) :element-type 'list
                            :initial-element nil)))
    (loop for (u v) on lst by #'cddr 
          while v
          do
             (push v (aref ary u))
             (push u (aref ary v)))
    ary))

;; (edges->nodes *e* 2) ;=> (3 4 1)
(defun edges->nodes (edges n)
  (aref edges n))

;; (make-holidays 4 3 '("xxo" "xox" "oxo" "oxx")) ;=>
;; #2A((NIL NIL NIL NIL)
;;     (NIL NIL NIL T)
;;     (NIL NIL T NIL)
;;     (NIL T NIL T)
;;     (NIL T NIL NIL))
(defun make-holidays (N W lines)
  (let* ((ary2d (make-array (list (1+ N) (1+ W))
                            :element-type 'boolean
                            :initial-element nil)))
    (loop for city from 1 to N
          for line in lines
          do (loop for day from 1 to W
                   for ch across line
                   do (setf (aref ary2d city day)
                            (eql ch #\o))))
    ary2d))


(defun cycle (x cycle)
  (1+ (mod (1- x) cycle)))

;; (next-holiday-cities 3 2 3 *e* *h*) ;=> (3 1)
;; (next-holiday-cities 3 2 4 *e* *h*) ;=> (3 4)
;; (next-holiday-cities 3 1 2 *e* *h*) ;=> (2)
(defun next-holiday-cities (W current-city next-day edges holidays)
  (loop for city in (cons current-city
                          (edges->nodes edges current-city))
        when (aref holidays city (cycle next-day W))
          collect city))

;; (holiday-cities 4 3 1 *h*) ;=>  (3 4)
(defun holiday-cities (N W day holidays)
  (loop for city from 1 to N
        when (aref holidays city (cycle day W))
          collect city))

;; (next-cities 3 '(1 2 3 4) 2 *e* *h*) ;=> (2)
;; (next-cities 3 '(2) 3 *e* *h*)       ;=> (3 1)
;; (next-cities 3 '(3 1) 4 *e* *h*)     ;=> (4)
;; (next-cities 3 nil 4 *e* *h*)        ;=> NIL
(defun next-cities (W cities next-day edges holidays)
  (loop for city in cities
        append (next-holiday-cities
                W city next-day edges holidays) into result
        finally (return (remove-duplicates result))))

;; (next-week-cities 4 3 *e* *h* 1) ;=> (3 4)
(defun next-week-cities (N W edges holidays city)
  (declare (ignore N))
  (loop for day from 2 to (1+ W)
        with current-cities = (list city)
        while current-cities
        do (setf current-cities
                 (next-cities
                  W current-cities day edges holidays))
        finally (return current-cities)))

;; (week-edges 4 3 *e* *h*) ;=> #(NIL NIL NIL (4 3) (4 3))
(defun week-edges (N W edges holidays)
  (let* ((start-cities (holiday-cities N W 1 holidays))
         (ary (make-array (1+ N) :element-type 'list
                                 :initial-element nil)))
    (loop for ca in start-cities
          do (loop for cb in (next-week-cities N W edges holidays ca)
                   do (push cb (aref ary ca)))
          finally (return ary))))

;; (has-cycle-p 4 #(NIL NIL NIL (4 3) (4 3))) ;=> T
;; (has-cycle-p 4 #(NIL NIL NIL (4) (1))) ;=> NIL
(defun has-cycle-p (N edges)
  (let* ((state (make-array (1+ N) :initial-element :before)))
    (labels ((visit (v)
               (case (aref state v)
                 (:now t)
                 (:done nil)
                 (t (setf (aref state v) :now)
                  (let ((found-cycle (some #'visit (aref edges v))))
                    (setf (aref state v) :done)
                    found-cycle)))))
      (loop for i from 1 to N
            thereis (visit i)))))

(defun endless-holiday-p/graph (N W edges holidays)
  (has-cycle-p N (week-edges N W edges holidays)))

(defun print-yesno (c)
  (declare (type boolean c))
  (princ (if c "Yes" "No")))

(defun main ()
  (let* ((times (read)))
    (loop repeat times
          for N = (read)
          for M = (read)
          for edge-list = (loop repeat (* M 2)
                                collect (read))
          for W = (read)
          for lines = (loop repeat N
                            collect (read-line))
          for edges = (make-edges N edge-list)
          for holidays = (make-holidays N W lines)
          do (print-yesno (endless-holiday-p/graph
                           N W edges holidays))
             (terpri))))

#-swank(main)
Code language: Lisp (lisp)