public static void solveOne() throws Exception {
    int n = nextInt();
    int m = nextInt();
    int k = nextInt();
    int w = nextInt();
    char[][][] levels = new char[k][n][];
    ;
    for (int i = 0; i < k; i++) {
      for (int j = 0; j < n; j++) {
        levels[i][j] = nextString().toCharArray();
      }
    }
    int[][] cost = new int[k][k];
    PriorityQueue<int[]> q =
        new PriorityQueue<>(
            k * k,
            new Comparator<int[]>() {
              @Override
              public int compare(int[] o1, int[] o2) {
                return o1[2] - o2[2];
              }
            });
    for (int i = 0; i < k; i++) {
      for (int j = i + 1; j < k; j++) {
        int[] cur = {i, j, diff(levels[i], levels[j]) * w};
        cost[i][j] = cur[2];
        q.add(cur);
        //              px("edge", cur);
      }
    }
    //      px(q.size());
    //      for (int[] e: cost) px(e);
    DisjointUnionSet djs = new DisjointUnionSet(k);
    boolean[][] al = new boolean[k][k];
    int finalCost = n * m * k;
    for (; q.size() > 0; ) {
      int[] edge = q.poll();
      int p1 = djs.getPartitionId(edge[0]);
      int p2 = djs.getPartitionId(edge[1]);
      //          px(edge, p1 == p2, edge[2]);
      if (edge[2] > n * m) break;
      if (p1 != p2) {

        djs.unionElement(edge[0], edge[1]);
        finalCost -= n * m - edge[2];
        al[edge[1]][edge[0]] = true;
        al[edge[0]][edge[1]] = true;
      }
    }
    //      for (boolean[] e: al) px(e);
    pn(finalCost);
    boolean[] ex = new boolean[k];
    for (int i = 0; i < k; i++) {
      if (!ex[i]) {
        dfs(i, -1, ex, k, al);
      }
    }
  }
  public static void dfs(int at, int from, boolean[] ex, int k, boolean[][] al) {
    ex[at] = true;

    pn((at + 1) + " " + (from + 1));
    for (int i = 0; i < k; i++) {
      if (!ex[i] && al[i][at]) {
        //              px(i, at);
        dfs(i, at, ex, k, al);
      }
    }
  }