import static java.util.Arrays.fill;
 
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
 
public class ascii_keka {
     
    static final int DI[] = {1, 0, -1, 0};
    static final int DJ[] = {0, 1, 0, -1};
     
    static class Edge {
        final int dir[];
 
        public Edge(int[] dir) {
            this.dir = dir;
        }
 
        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + Arrays.hashCode(dir);
            return result;
        }
 
        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            Edge other = (Edge) obj;
            if (!Arrays.equals(dir, other.dir))
                return false;
            return true;
        }
 
        public String toString() {
            return "Edge [dir=" + Arrays.toString(dir) + "]";
        }
    }
     
    private static void solve() throws IOException {
        int k = nextInt();
        int w = nextInt();
        int h = nextInt();
        int edges[][] = new int[k][4];
        edgeTypes = new HashMap<>();
        edgeTypes.put(new Edge(new int[h - 2]), 0);
        {
            int a[] = new int[w - 2];
            fill(a, 1);
            edgeTypes.put(new Edge(a), 1);
        }
        char datas[][][] = new char[k][][];
        int lefts = 0;
        int rights = 0;
        int tops = 0;
        int bottoms = 0;
//      System.err.println(edgeTypes);
        for (int i = 0; i < k; i++) {
            char data[][] = new char[3 * h - 2][];
            for (int j = 0; j < 3 * h - 2; j++) {
                data[j] = nextToken().toCharArray();
//              System.err.println(data[j]);
            }
            edges[i][0] = traceEdge(data, h - 1, w - 1, 0, 2 * h - 2, w - 1, 0, false);
            edges[i][1] = traceEdge(data, 2 * h - 2, w - 1, 1, 2 * h - 2, 2 * w - 2, 1, false);
            edges[i][2] = traceEdge(data, 2 * h - 2, 2 * w - 2, 2, h - 1, 2 * w - 2, 2, true);
            edges[i][3] = traceEdge(data, h - 1, 2 * w - 2, 3, h - 1, w - 1, 3, true);
//          System.err.println(edges[i][0] + " " + edges[i][1] + " " + edges[i][2] + " " + edges[i][3]);
            datas[i] = data;
            if (edges[i][0] == 0) {
                ++lefts;
            }
            if (edges[i][1] == 1) {
                ++bottoms;
            }
            if (edges[i][2] == 0) {
                ++rights;
            }
            if (edges[i][3] == 1) {
                ++tops;
            }
        }
//      System.err.println(lefts + " " + rights + " " + tops + " " + bottoms);
        if (lefts != rights || tops != bottoms || lefts * tops != k) {
            throw new AssertionError();
        }
        int width = tops;
        int height = lefts;
        int ans[][] = new int[height][width];
        boolean used[] = new boolean[k];
        if (!go(edges, used, ans, 0, 0)) {
            throw new AssertionError();
        }
        char ansData[][] = new char[h * height][w * width];
        for (int i = 0; i < height; i++) {
            for (int j = 0; j < width; j++) {
                int cur = ans[i][j];
                for (int ii = 0; ii < 3 * h - 2; ii++) {
                    for (int jj = 0; jj < 3 * w - 2; jj++) {
                        if (datas[cur][ii][jj] != '.') {
                            ansData[h * i + ii - (h - 1)][w * j + jj - (w - 1)] = (char) ('A' + cur);
                        }
                    }
                }
            }
        }
        out.println(w * width + " " + h * height);
        for (char a[]: ansData) {
            out.println(a);
        }
    }
     
    static Map<Edge, Integer> edgeTypes;
     
    static int traceEdge(char data[][], int i, int j, int d, int ri, int rj, int rd, boolean reverse) {
//      for (char a[]: data) {
//          System.err.println(a);
//      }
//      System.err.println(ri + " " + rj + " " + rd);
        List<Integer> res = new ArrayList<>();
        do {
//          System.err.println(i + " " + j + " " + d);
//          if (res.size() > 100) {
//              throw new AssertionError();
//          }
            int ni = i + DI[d];
            int nj = j + DJ[d];
            if (!at(data, ni, nj)) {
//              System.err.println("Path 1");
                d = (d + 1) & 3;
            } else {
                int nni = ni + DI[(d - 1) & 3];
                int nnj = nj + DJ[(d - 1) & 3];
                if (!at(data, nni, nnj)) {
//                  System.err.println("Path 2");
                    i = ni;
                    j = nj;
                } else {
//                  System.err.println("Path 3");
                    i = nni;
                    j = nnj;
                    d = (d - 1) & 3;
                }
            }
            res.add(d);
        } while (i != ri || j != rj || d != rd);
        int ans[] = new int[res.size() - 1];
        if (reverse) {
            for (int k = 0; k < ans.length; k++) {
                ans[k] = res.get(ans.length - k - 1) ^ 2;
            }
        } else {
            for (int k = 0; k < ans.length; k++) {
                ans[k] = res.get(k);
            }
        }
//      System.err.println(Arrays.toString(ans));
        Edge e = new Edge(ans);
        Integer ee = edgeTypes.get(e);
//      System.err.println(ee);
        if (ee == null) {
            int ret = edgeTypes.size();
            edgeTypes.put(e, ret);
            return ret;
        } else {
            return ee;
        }
    }
     
    static boolean at(char data[][], int i, int j) {
        if (i < 0 || i >= data.length) {
            return false;
        }
        if (j < 0 || j >= data[i].length) {
            return false;
        }
        return data[i][j] != '.';
    }
     
    static boolean go(int edges[][], boolean used[], int ans[][], int i, int j) {
        int h = ans.length;
        if (i >= h) {
//          System.err.println("OK");
            return true;
        }
        int w = ans[i].length;
        if (j >= w) {
            return go(edges, used, ans, i + 1, 0);
        }
        for (int piece = 0; piece < used.length; piece++) {
            if (used[piece] || (edges[piece][0] == 0) != (j == 0) ||
                (edges[piece][1] == 1) != (i == h - 1) ||
                (edges[piece][2] == 0) != (j == w - 1) ||
                (edges[piece][3] == 1) != (i == 0) ||
                (i > 0 && edges[piece][3] != edges[ans[i - 1][j]][1]) ||
                (j > 0 && edges[piece][0] != edges[ans[i][j - 1]][2])) {
                continue;
            }
//          System.err.println("> " + i + " " + j + " " + piece);
            used[piece] = true;
            ans[i][j] = piece;
            if (go(edges, used, ans, i, j + 1)) {
                return true;
            }
            used[piece] = false;
//          System.err.println("< " + i + " " + j + " " + piece);
        }
        return false;
    }
 
    public static void main(String[] args) throws IOException {
        br = new BufferedReader(new FileReader("ascii.in"));
        out = new PrintWriter("ascii.out");
        solve();
        out.close();
    }
 
    static BufferedReader br;
    static PrintWriter out;
    static StringTokenizer st;
 
    static String nextToken() throws IOException {
        while (st == null || !st.hasMoreTokens()) {
            String line = br.readLine();
            if (line == null) {
                return null;
            }
            st = new StringTokenizer(line);
        }
        return st.nextToken();
    }
 
    static int nextInt() throws IOException {
        return Integer.parseInt(nextToken());
    }
 
    static long nextLong() throws IOException {
        return Long.parseLong(nextToken());
    }
}