\documentclass[10pt,french]{article}
\usepackage[french]{babel}
\usepackage[utf8]{inputenc}

\usepackage{amsmath,amssymb,fancybox,fancyhdr,lastpage,a4,mdwlist}

\topmargin-0.5cm
\headheight20pt
\textheight23cm
\textwidth17.5cm
\oddsidemargin-1cm
\evensidemargin0cm
\parindent0pt

\def\[{[\![}
\def\]{]\!]}

\begin{document}

\renewcommand{\labelenumi}{\arabic{enumi})}
\renewcommand{\labelenumii}{\alph{enumii})}
\renewcommand{\labelenumiii}{\roman{enumiii})}

\pagestyle{fancy}
\lhead{{\bf MPSI option info}}
\chead{}
\rhead{corrigé TP 4~: impératif}
\lfoot{\sl Lycée Chateaubriand}
\cfoot{}
\rfoot{\thepage/\pageref{LastPage}}

\renewcommand{\footrulewidth}{0.4pt}

\section{Exercice du cours}

\subsection*{Liste chaînée}
\label{sec:orge1aeabf}
\begin{verbatim}
let l4 = 4 in
let l3 = 3, ref l4 in
let l2 = 2, ref l3 in
let l1 = 1, ref l2 in
         0, ref l1;;
\end{verbatim}
l'objet construit est de type \texttt{int * (int * (int * (int * int ref) ref) ref) ref}.

L'accès au 2ème élément (en comptant à partir de 0) d'une telle liste \texttt{l} se fait par
\begin{verbatim}
fst !(snd !(snd l));;
\end{verbatim}

\section{Le crible d'ératosthène}
\label{sec:org9e6c51c}

\subsection*{Question 1}
\label{sec:orgb987bad}
On crée un tableau \texttt{crible : bool array} de taille $n+1$ commençant à zéro pour garantir que \texttt{crible.(i)} corresponde bien à la propriété \texttt{i} est premier (et pas \texttt{i+1}).
\begin{verbatim}
let eratosthene n =
  let crible = Array.make (n+1) true in
  crible.(0) <- false;
  crible.(1) <- false;
  let i = ref 2 in
  let k = ref 0 in
  while !i * !i <= n do
    if crible.(!i) then
      (k := 2* !i;
      while !k <= n do
        crible.(!k) <- false;
        k:=!k+ !i
      done);
    incr i;
  done;
  crible;;
\end{verbatim}

\subsection*{Question 2}
\label{sec:orgafebda3}
Si $s$ est impair et s'écrit comme somme de deux nombres premiers alors nécessairement l'un des deux est $2$. On effectue donc aucun parcours dans ce cas.

Sinon on parcourt le tableau en vérifiant à chaque $k$ premier si $s-k$ est premier.

\begin{verbatim}
let decompose_somme t s =
  if  s>2 && (s=4 || s mod 2 = 1) then
    t.(s-2)
  else
    (let b = ref false in
     let k = ref 3 in
     while not !b && 2* !k <= s do
       b :=  t.(!k) && t.(s- !k);
       k := !k+2
     done;
     !b);;

(* en admettant la conjecture de Goldbach
   voir https://fr.wikipedia.org/wiki/Conjecture_de_Goldbach
*)
let decompose_somme_Gb t s = (s>2) && (s mod 2 = 0 || t.(s-2) );;
\end{verbatim}

\section{Polynômes}
\label{sec:org5aaae59}

\subsection*{1)}
\label{sec:org324586c}
On ne change qu'une seule chose le \texttt{r.(i) + q.(i)} devient une soustraction
\begin{verbatim}
(* soustraction *)
let (--) p q =
  let lp = Array.length p
  and lq = Array.length q
  in
  let r = Array.make (max lp lq) 0 in
  for i=0 to lp-1 do
    r.(i) <- p.(i)
  done;
  for i=0 to lq-1 do
    r.(i) <- r.(i) - q.(i)
  done;
  r;;
\end{verbatim}
et pour la multiplication on parcourt les degrés $k$ entre $0$ et la somme des degrés de \texttt{p} et \texttt{q}. Et pour chaque $k$ on a $k+1$ manière d'obtenir $x^k$ : tous les $x^ix^j$ pour $i+j = k$. On parcourt donc ces possibilités en ajoutant à chaque fois le terme obtenu par le produit du monôme de degré $i$ de $p$ et du monôme de degré $j$ de $q$.
\begin{verbatim}
(* multiplication *)
let ( ** ) p q =
  let lp = Array.length p
  and lq = Array.length q
  in
  let r = Array.make (lp + lq) 0 in
  for k = 0 to (lp+lq-1) do
    for i = 0 to min k (lp-1) do
      let j = k - i in
      if j < lq then
        r.(k) <- r.(k) + p.(i)*q.(j) ;
    done;
  done;
  r;;
\end{verbatim}

\subsection*{2) Tchebychev}
\label{sec:org07661b7}

\subsubsection*{Mauvaise solution récursive}
\label{sec:org0b39cc4}
On reconnaît le calcul na\"{\i}f de Fibonacci donc la complexité \emph{en nombre d'opérations sur les polynômes} est exponentielle de base $\phi$.
\begin{verbatim}
let rec tchebychev k =
  if k = 0 then
    [| 1 |]
  else if k = 1 then
    [| 0 ; 1 |]
  else
    [| 0 ; 2 |] ** tchebychev (k-1) -- tchebychev (k-2);;
\end{verbatim}

On vérifie néanmoins que cette première solution fonctionne sur l'exemple demandé (selon l'implémentation, il y a plus ou moins de zéros inutiles à la fin du tableau) :
\begin{verbatim}
# tchebychev 7;;
- : int array = [|0; -7; 0; 56; 0; -112; 0; 64; 0; 0; 0; 0; 0; 0|]
\end{verbatim}

\subsubsection*{Solution tableau}
\label{sec:org0f7af51}
On crée une matrice de taille $(k+1) \times (k+1)$ telle que la ligne $i$ contienne les coefficients de $T_i$. En effet on montre facilement par récurrence que $T_i$ est de degré $i$.
\begin{verbatim}
let tchebychev k =
  let t = Array.make_matrix (k+1) (k+1) 0 in
  t.(0).(0) <- 1 ;
  t.(1).(1) <- 1 ;
  for i = 2 to k do
    let ti = t.(i-1) ** [|0;2|] -- t.(i-2) in
    let lti = Array.length ti in
    for j = 0 to min k (lti-1) do
      t.(i).(j) <- ti.(j);
    done;
  done;
  t.(k);;
\end{verbatim}

\subsubsection*{Solution récursive terminale}
\label{sec:org42623e0}
On garde à chaque coup en mémoire les $T_i$ et $T_{i-1}$.
\begin{verbatim}
let tchebychev k =
  let rec tchebychev_recursive d t_prec t_ante =
    if d = k then
      t_ante
    else
      tchebychev_recursive (d+1) ([|0;2|] ** t_prec -- t_ante) t_prec
  in
  tchebychev_recursive 0 [|0;1|] [|1|];;
\end{verbatim}

\subsection*{3) Composition}
\label{sec:orgd38431f}

\begin{verbatim}
let compose p q =
  let n = Array.length p in
  let rec compose_Horner p q i =
    if i >= n then
      [||]
    else
      [|p.(i)|] ++ (q ** (compose_Horner p q (i+1)))
  in
  compose_Horner p q 0;;
\end{verbatim}

\subsection*{4) Division Euclidienne}
\label{sec:org211dbc0}
\begin{enumerate}
\item Il s'agit de l'algorithme de division euclidienne.

\item à chaque étape de la boucle, si la condition est vérifiée, l'opération $q_i X^i B$ coûte $m$ multiplications et la soustraction à $R$ de $q_i X_i B$ coûte $\deg B = m$ additions car on ne modifie pas les autres coefficients de $R$. Ainsi on a $2m$ opérations auxquelles il faut ajouter le calcul $cd(R)u$.
   Une itération de la boucle coûte donc au plus $2m+1$, elles sont répétés $n-m+1$ fois et l'initialisation de $u$ coûte une inversion. Ainsi on a une complexité en $O(2m(n-m))$ or si $n < m$ l'algorithme s'arrête immédiatement donc $O(mn)$.
\item

On ne peut pas l'appliquer dans $\mathbb Z[X]$ car il n'est pas euclidien.
Par exemple $X^2 - 1$ ne peut pas être divisé par $3X$ dans $\mathbb Z[X]$ puisque $1/3$ n'existe pas.

\item
\begin{verbatim}
let degre p =
  let n = ref ((Array.length p) - 1) in
  while p.(!n) = 0. do
    decr n;
  done;
  !n
;;

let division a b =
  let n = degre a in
  let m = degre b in
  if  n < m then
    [|0.|], a
  else
    let r = Array.copy a in
    let q = Array.make (n-m+1) 0. in
    let u = (1. /. b.(m)) in
    for i = (n-m) downto 0 do
      let d = degre r in
      if d = m+i then
        begin
          q.(i) <- r.(d) *. u;
          for j = 0 to m do
            r.(j+i) <- r.(j+i) -. q.(i) *. b.(j)
          done;
        end
      else
        q.(i) <- 0.;
    done;
    q, r;;
\end{verbatim}
\end{enumerate}

\section{Jeu de Hex}
\label{sec:org486b932}

On distingue tous les cas particuliers suivant $i,j$.

\begin{verbatim}
let voisins i j n =

  if i = 0 then                (* première ligne *)

    if j = 0 then              (* - coin en haut à gauche *)
      [(0,1);(1,0)]
    else if j = n-1 then       (* - coin en haut à droite *)
      [(0,n-1);(1,n-1);(1,n-2)]
    else                       (* - première ligne, cas général *)
      [(i+1,j) ; (i,j-1) ; (i,j+1) ; (i+1,j-1)]

  else if i  = n - 1 then      (* dernière ligne *)

    if j = 0 then              (* - coin en bas à gauche *)
      [(i-1,0);(i-1,1);(i,1)]
    else if j = n-1 then       (* - coin en bas à droite *)
      [(i,n-2);(i-1,n-1)]
    else                       (* - dernière ligne, cas général *)
      [(i-1,j) ; (i,j-1) ; (i,j+1) ; (i-1,j-1)]

  else                         (* ligne générale *)

    if j = 0 then              (* - colonne gauche *)
      [(i-1,j) ; (i-1,j+1) ; (i,j+1) ; (i+1,j)]
    else if j = n-1 then       (* - colonne droite *)
      [(i-1,j) ; (i+1,j-1) ; (i,j-1) ; (i+1,j)]
    else                       (* - cas général *)
    [(i-1,j) ; (i+1,j) ; (i,j-1) ; (i,j+1) ; (i-1,j+1) ; (i+1,j-1)];;
\end{verbatim}

On décompose de façon suivante
\begin{itemize}
\item Une fonction \texttt{init : unit -> bool * int * int * ( int -> int -> bool)} qui pour un joueur fixé cher- che une de ses pièces sur la première ligne ou la première colonne (si le joueur est vainqueur une telle pièce existe forcément). Elle renvoie un quadruple $(b,i_0,j_0,c)$ o\`u \texttt{b} indique l'existence de la pièce recherchée, $(i_0,j_0)$ ses coordonnées (si elle existe), et si en partant de $(i_0,j_0)$ on visite à un certain moment la case $(i,j)$, alors $c i j$ indique si la case atteint le bord \emph{opposé} à $(i_0,j_0)$.
\item La fonction \texttt{parcours : (int * int) list -> (int -> int -> bool) -> bool} qui effectue le parcours des pièces du jeu. à chaque pièce non-finale appartenant au joueur courant elle ajoute ses voisins au parcours. En cas d'échec du parcours elle tente de relancer \texttt{init} (repartir d'une nouvelle pièce) et en cas de nouvel échec elle renvoie \texttt{false}.
\end{itemize}

\begin{verbatim}
let hex jeu =
  let n = Array.length jeu in
  let plateau = Array.make_matrix n n ' ' in
  for i = 0 to n-1 do
    plateau.(i) <- Array.copy jeu.(i)
  done;  (* On commence par copier le plateau pour pouvoir le modifier *)
  let minusc = function 'O' -> 'o' | 'X' -> 'x' | c -> c in
  (* On indique par des minuscules les cases déjà visités *)
  let cherchevictoire joueur =
    let init () = (* cherche une pièce du joueur sur le bord haut ou gauche *)
      let k = ref 0 in
      let i0 = ref (-1) in
      let j0 = ref (-1) in
      while !k < n do
        begin
          if plateau.(!k).(0) = joueur then
            begin
              i0 := !k;
              j0 := 0;
              plateau.(!k).(0) <- minusc joueur; (* on marque la pièce comme étant vue *)
              k := n;
            end
          else if plateau.(0).(!k) = joueur then
            begin
              i0 := 0;
              j0 := !k;
              plateau.(0).(!k) <- minusc joueur;
              k := n;
            end;
        end;
        incr k;
      done;
      let cond_victoire i j = if !i0 = 0 then i = n-1 else j = n-1 in
      if !k = n+1 then
        (true, !i0, !j0, cond_victoire)
      else
        (false, -1, -1, fun i j -> false)
    in
    let rec parcours l condition_victoire=
      match l with
      | [] -> let b, i0,j0, c_v = init () in
              if b then
                parcours (voisins i0 j0 n) c_v
              else false
      | (i,j) :: q when (plateau.(i).(j) = joueur) && (condition_victoire i j) ->  true
      | (i,j) :: q when plateau.(i).(j) = joueur -> begin
          plateau.(i).(j) <- minusc joueur;
          parcours (l @ (voisins i j n)) condition_victoire
        end
      | _ :: q -> parcours q condition_victoire
    in
  parcours [] (fun i j -> false)
  in
  if cherchevictoire 'O' then
    "vainqueur : O"
  else if cherchevictoire 'X' then
      "vainqueur : X"
  else
    "pas (encore) de vainqueur";;
\end{verbatim}
\end{document}



