Skip to content

Instantly share code, notes, and snippets.

@kik
Last active April 16, 2016 14:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kik/1a058297cd7441007b00dd88740d6d2b to your computer and use it in GitHub Desktop.
Save kik/1a058297cd7441007b00dd88740d6d2b to your computer and use it in GitHub Desktop.
GCJ 2016 予選B問題

GCJ 2016 予選B問題を解いた

一週間かかりました。

この問題は本当に難しいです。場合分けが多いし、リストの反転は難しいし、最小値であることの証明は当然大変です。

B問題

  • bool のリスト ps が与えられる
  • 以下の操作を繰り返す
  • ps を任意の位置で前半の qs と後半 rs に分割して
  • qs の true/false を反転して
  • qs の逆順にして
  • qs と rs を連結して新しい ps とする
  • ps がすべて true になるまでに必要な最小操作回数を計算せよ
(*
 * 問題の定義
 *)
Definition happiest (ps : list bool) : bool :=
  forallb id ps.

Definition flip (ps : list bool) (n : nat) : list bool :=
  let h := firstn n ps in
  let t := skipn  n ps in
  rev (map negb h) ++ t.

Definition flips (ps : list bool) (ns : list nat) : list bool :=
  fold_left flip ns ps.

Definition make_happiest (ps : list bool) (ns : list nat) : bool :=
  happiest (flips ps ns).

Definition answer_spec (answer : list bool -> nat) :=
  forall (ps : list bool),
    (exists moves : list nat, make_happiest ps moves /\ length moves = answer ps) /\
    (forall moves : list nat, make_happiest ps moves -> length moves >= answer ps).

問題を定義するだけなら簡単ですね。

answer_spec answer を満たす answer 関数を頑張って作りましょう!

最良の手を計算する

Fixpoint best_move (ps : list bool) : nat :=
  match ps with
    | nil => 0
    | p :: ps =>
      match ps with
        | nil => 1
        | q :: _ =>
          if bool_dec_negb p q then
            1 + best_move ps
          else
            1
      end
  end.

これで最良の手が計算できます。リスト ps に対して、前半の長さが best_move ps になるように分割するのが最良です。

計算例は次のようになります。簡単ですね。

best_move [] = 0
best_move [true] = 1
best_move [false] = 1
best_move [true; true; ps] = 1 + best_move [true; ps]
best_move [false; false; ps] = 1 + best_move [false; ps]
best_move [true; false; ps] = 1
best_move [false; true; ps] = 1

最初の2つは実際には起きないので、都合のいい値を割り当てておきます。

残りの場合は、true/false が反転する位置を返します。

答えを計算する

最小の操作数も計算します。

Fixpoint count (ps : list bool) : nat :=
  match ps with
    | nil => 0
    | p :: ps =>
      match ps with
        | nil => 1
        | q :: qs =>
          if bool_dec_negb q p then
            count ps
          else
            1 + count ps
      end
  end.

Definition answer (ps : list bool) : nat :=
  count (ps ++ true :: nil) - 1.

分岐の構造はさっきのと同じです。計算例は

count [] = 0
count [true] = 1
count [false] = 1
count [true; true; ps] = count [true; ps]
count [false; false; ps] = count [false; ps]
count [true; false; ps] = 1 + count [false; ps]
count [false; true; ps] = 1 + count [true; ps]

こっちは同じ bool 値が隣り合ってるところを1つに潰して、長さを計算するだけです。

最終的な答えは、後ろに true を付けてから、count を計算して、1を引きます。

countの性質を証明する

countanswerより性質がよいです。証明したい性質は

true/false を反転してもcountは変わらない

当たり前だ。

count ps = count (map negb ps)

リストを前後反転してもcountは変わらない

当たり前だ?これは難しいぞ!

count ps = count (rev ps)

リストを結合するとcountは足し算になりそう

たまに結果が1ずれるよ!

count ps + count qs = count (ps ++ qs)

または

count ps + count qs = 1 + count (ps ++ qs)

ずれるのは、リストが両方とも非空で、psの末尾とqsの先頭が一致するときですね。

count (map negb ps)count (ps ++ qs)については簡単に証明できる

それに比べてcount (rev ps)はちょっとめんどくさいです。と思わせておいて、簡単な方法があります。

rev++をまとめて証明すると簡単になります。count (rev_append ps qs)を使いましょう。

rev_append ps qs = rev ps ++ qs

なので、たまに結果が1ずれます。ずれる条件は

Definition rev_app_decr (ps qs : list bool) : bool :=
  match ps with
    | nil => false
    | p :: ps =>
      match qs with
        | nil => false
        | q :: qs =>
          if bool_dec_negb q p then
            true
          else
            false
      end
  end.

trueになる場合ですね。要するに、psqsが両方とも非空で、先頭が一致するときです。

証明したいことは

Lemma rev_app_count:
  forall (ps qs : list bool),
    count ps + count qs =     
    if rev_app_decr ps qs then
      1 + count (rev_append ps qs)
    else
      count (rev_append ps qs).

になります。これはpsについての帰納法で簡単にできます。なぜかというと、psp :: psの場合は

count (rev_append (p :: ps) qs) = count (rev_append ps (p :: qs))

になるので、帰納法の仮定が簡単に使えるからです。

まとめると

一回の操作では、rev_append (map negb ps) qs)が行われるので、countはたまに1ずれます。ずれる条件は

Definition decr (ps qs : list bool) : Prop :=
  match ps with
    | nil => False
    | p :: _ =>
      match rev ps with
        | nil => False
        | q :: _ =>
          match qs with
            | nil => False
            | r :: _ =>
              p = q /\ p <> r
          end
      end
  end.

になります。両方が非空で、psの先頭と末尾が一致していて、qsの先頭と不一致の場合です。

というわけで、

Lemma decr_count:
  forall ps qs,
    decr ps qs ->
    count (ps ++ qs) = 1 + count (rev_append (map negb ps) qs).

answerの性質

answer psがゼロの場合は、psはすでにすべてtrueです。逆も成り立ちます。

Lemma answer_zero_happiest :
  forall (ps : list bool),
    answer ps = 0 <-> happiest ps.

当たり前ですね。

一回の操作でanswerが2以上減ることはありません。

Lemma flip_answer_le:
  forall (ps : list bool) (n : nat),
    answer ps <= 1 + answer (flip ps n).

これらにより、最小手数がanswerを下回ることはないことが分かります。

best_moveを行うと、answerがちょうど1減ります。

Lemma best_move_answer:
  forall (ps : list bool),
    happiest ps \/ answer ps - 1 = answer (flip ps (best_move ps)).

この証明が一番難しいです。頑張りました。

answerが正しいことの証明

あとはやるだけです。

Theorem answer_is_correct: answer_spec answer.
Require Import Arith Even Bool List Omega.
Local Coercion is_true : bool >-> Sortclass.
Set Implicit Arguments.
(*
* 問題の定義
*)
Definition happiest (ps : list bool) : bool :=
forallb id ps.
Definition flip (ps : list bool) (n : nat) : list bool :=
let h := firstn n ps in
let t := skipn n ps in
rev (map negb h) ++ t.
Definition flips (ps : list bool) (ns : list nat) : list bool :=
fold_left flip ns ps.
Definition make_happiest (ps : list bool) (ns : list nat) : bool :=
happiest (flips ps ns).
Definition answer_spec (answer : list bool -> nat) :=
forall (ps : list bool),
(exists moves : list nat, make_happiest ps moves /\ length moves = answer ps) /\
(forall moves : list nat, make_happiest ps moves -> length moves >= answer ps).
(*
* 便利関数
*)
Definition bool_dec_negb (x y : bool) : { x = y } + { x = negb y }.
Proof.
destruct x; destruct y; [left|right|right|left]; reflexivity.
Defined.
Fixpoint rep (n : nat) (p : bool) : list bool :=
match n with
| O => nil
| S n => p :: rep n p
end.
(*
* 最良の着手
*)
Fixpoint best_move (ps : list bool) : nat :=
match ps with
| nil => 0
| p :: ps =>
match ps with
| nil => 1
| q :: _ =>
if bool_dec_negb p q then
1 + best_move ps
else
1
end
end.
(*
* 答えの計算
*)
Fixpoint count (ps : list bool) : nat :=
match ps with
| nil => 0
| p :: ps =>
match ps with
| nil => 1
| q :: qs =>
if bool_dec_negb q p then
count ps
else
1 + count ps
end
end.
Definition answer (ps : list bool) : nat :=
count (ps ++ true :: nil) - 1.
(*
* 後は証明を頑張る
*)
Lemma count_nil:
count nil = 0.
Proof. auto. Qed.
Lemma count_1:
forall p, count (p :: nil) = 1.
Proof. intro p; destruct p; auto. Qed.
Lemma count_e:
forall p ps, count (p :: p :: ps) = count (p :: ps).
Proof. intros p ps; destruct p; simpl; auto. Qed.
Lemma count_n:
forall p ps, count (negb p :: p :: ps) = 1 + count (p :: ps).
Proof. intros p ps; destruct p; simpl; auto. Qed.
Definition rev_app_decr (ps qs : list bool) : bool :=
match ps with
| nil => false
| p :: ps =>
match qs with
| nil => false
| q :: qs =>
if bool_dec_negb q p then
true
else
false
end
end.
Lemma rev_app_count:
forall (ps qs : list bool),
count ps + count qs =
if rev_app_decr ps qs then
1 + count (rev_append ps qs)
else
count (rev_append ps qs).
Proof.
intro ps.
destruct ps as [|p ps].
simpl; auto.
revert p.
induction ps as [|p' ps].
simpl.
intros p qs.
destruct qs as [|q qs]; auto.
destruct bool_dec_negb; auto.
intros p qs.
specialize (IHps p' (p :: qs)).
revert IHps.
unfold rev_app_decr.
destruct qs as [|q qs].
destruct bool_dec_negb.
rewrite e, count_e, count_nil, count_1; simpl; intros; omega.
rewrite e, count_n, count_nil, count_1; simpl; intros; omega.
destruct bool_dec_negb.
rewrite e, count_e.
destruct bool_dec_negb.
rewrite e0, count_e; auto.
replace p' with (negb q).
rewrite count_n.
simpl; intros; omega.
destruct q; destruct p'; auto.
rewrite e.
destruct bool_dec_negb.
rewrite e0, count_e, count_n; simpl; intros; omega.
rewrite e0, negb_involutive, !count_n; simpl; intros; omega.
Qed.
Lemma rev_count:
forall ps,
count ps = count (rev ps).
Proof.
intro ps.
specialize (rev_app_count ps nil).
destruct ps; simpl; auto.
rewrite rev_append_rev.
intro H; rewrite <-H; auto.
Qed.
Lemma app_count:
forall ps qs,
count ps + count qs =
if rev_app_decr (rev ps) qs then
1 + count (ps ++ qs)
else
count (ps ++ qs).
Proof.
intros ps qs.
rewrite <- (rev_involutive ps).
rewrite <- (rev_append_rev (rev ps) qs).
rewrite <- rev_count.
rewrite rev_involutive.
apply rev_app_count.
Qed.
Lemma negb_count:
forall ps,
count ps = count (map negb ps).
Proof.
intro ps.
destruct ps as [|p ps]; auto.
revert p.
induction ps as [|q ps]; auto.
intros p.
specialize (IHps q).
revert IHps.
rewrite !map_cons.
destruct (bool_dec_negb p q).
rewrite e, !count_e; auto.
rewrite e, !count_n; auto.
Qed.
Definition decr (ps qs : list bool) : Prop :=
match ps with
| nil => False
| p :: _ =>
match rev ps with
| nil => False
| q :: _ =>
match qs with
| nil => False
| r :: _ =>
p = q /\ p <> r
end
end
end.
Lemma decr_count:
forall ps qs,
decr ps qs ->
count (ps ++ qs) = 1 + count (rev_append (map negb ps) qs).
Proof.
intros ps qs.
unfold decr.
intros H.
specialize (app_count ps qs).
rewrite negb_count.
rewrite rev_app_count.
unfold rev_app_decr.
destruct ps as [|p ps]; try contradiction.
destruct (rev (p :: ps)) as [|q ps']; try contradiction.
destruct qs as [|r qs]; try contradiction.
destruct H as [H1 H2].
rewrite <- H1.
destruct p; destruct r; simpl; congruence.
Qed.
Lemma answer_zero_happiest :
forall (ps : list bool),
answer ps = 0 <-> happiest ps.
Proof.
induction ps as [|p ps].
split; auto.
destruct ps as [|q ps].
destruct p; compute; split; intro; congruence.
revert IHps.
unfold answer.
rewrite <- !app_comm_cons.
destruct (bool_dec_negb p q).
rewrite e, count_e.
intro H; rewrite H; clear H.
destruct q; simpl; reflexivity.
rewrite e, count_n; clear e p.
intros _.
replace (happiest _) with false.
revert q.
induction ps as [|p ps].
intro q; destruct q; compute; split; intro; congruence.
intro q.
rewrite <- !app_comm_cons.
destruct (bool_dec_negb q p).
rewrite e, count_e; apply IHps.
rewrite e, count_n; simpl; split; intro; congruence.
destruct q; simpl; auto.
Qed.
Lemma flip_answer_le:
forall (ps : list bool) (n : nat),
answer ps <= 1 + answer (flip ps n).
Proof.
intros ps n.
unfold answer, flip.
rewrite <- (firstn_skipn n ps) at 1.
rewrite <- !app_assoc.
generalize (firstn n ps).
generalize (skipn n ps ++ true :: nil).
clear ps n.
intros qs ps.
cut (count (ps ++ qs) <= 1 + (count (rev (map negb ps) ++ qs))).
intro; omega.
apply le_trans with (count ps + count qs).
rewrite app_count.
destruct rev_app_decr; auto with arith.
rewrite (negb_count ps).
rewrite rev_app_count.
rewrite rev_append_rev.
destruct rev_app_decr; auto.
Qed.
Lemma best_move_app:
forall ps,
let n := best_move ps in
firstn n ps = rep n (hd true ps).
Proof.
intros ps.
destruct ps as [|p ps].
simpl; auto.
revert p.
induction ps as [|q ps].
auto.
specialize (IHps q).
intro p.
simpl.
destruct bool_dec_negb; auto.
revert IHps.
simpl.
congruence.
Qed.
Lemma best_move_negb:
forall p ps,
head (skipn (best_move (p :: ps)) (p :: ps)) <> Some p.
Proof.
intros p ps.
revert p.
induction ps as [|q ps].
simpl; congruence.
specialize (IHps q).
intros p.
revert IHps.
simpl.
destruct bool_dec_negb.
simpl.
congruence.
simpl.
intros _.
destruct p; destruct q; simpl in e |- *; congruence.
Qed.
Lemma rev_rep:
forall p n,
rep n p = rev (rep n p).
Proof.
intro p.
induction n; auto.
simpl.
rewrite <- IHn.
clear IHn.
induction n; auto.
simpl.
congruence.
Qed.
Lemma best_move_answer:
forall (ps : list bool),
happiest ps \/ answer ps - 1 = answer (flip ps (best_move ps)).
Proof.
intro ps.
case_eq (happiest ps).
intro; left; auto.
intro H; right; revert H.
destruct ps as [|p ps].
unfold happiest; simpl; intro; congruence.
intro Hnh.
unfold answer.
unfold flip.
rewrite <- (firstn_skipn (best_move (p :: ps)) (p :: ps)) at 1.
rewrite <- app_assoc.
rewrite decr_count.
rewrite rev_append_rev.
rewrite <- app_assoc.
omega.
rewrite <- (firstn_skipn (best_move (p :: ps)) (p :: ps)) in Hnh.
revert Hnh.
rewrite best_move_app.
specialize (@best_move_negb p ps).
unfold decr.
rewrite <- rev_rep.
destruct best_move; simpl.
congruence.
destruct skipn as [|r qs].
simpl.
destruct p; simpl; auto.
intros _ H.
contradict H.
induction n; simpl; auto.
congruence.
simpl.
intros; split; congruence.
Qed.
Theorem answer_is_correct: answer_spec answer.
Proof.
unfold answer_spec.
intros ps.
split.
remember (answer ps) as n.
revert ps Heqn.
induction n.
intros ps Heqn.
exists nil; split; auto.
unfold make_happiest, flips; simpl.
apply answer_zero_happiest; auto.
intros ps Heq.
destruct (best_move_answer ps).
contradict Heq.
apply answer_zero_happiest in H.
rewrite H; auto.
destruct (IHn (flip ps (best_move ps))) as [moves [H1 H2]].
omega.
exists (best_move ps :: moves).
split; simpl; auto.
intros moves; revert ps.
induction moves as [|move moves].
unfold make_happiest, flips; simpl.
intros ps.
rewrite <- answer_zero_happiest.
omega.
intros ps H.
specialize (IHmoves (flip ps move)).
simpl.
apply le_trans with (1 + answer (flip ps move)).
apply flip_answer_le.
simpl.
apply le_n_S.
apply IHmoves.
auto.
Qed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment