Problem:
H: Rectangular Stamps

ICPC で良い成績を収めるには修行が欠かせない．うさぎは ICPC で勝ちたいので，今日も修行をすることにした．

今日の修行は，絵を描くことによって，創造力を高めようというものである．四角いスタンプを用いて，上手く模様を描こう．

大小さまざまなスタンプを使い，4 × 4 のマス目の紙に指定された赤・緑・青の通りの絵を完成させたい．スタンプは長方形であり，マス目にぴったり合わせて使う．スタンプの縦と横を入れ替えることはできない．

紙は最初色が塗られていない状態にある．紙にスタンプを押すと，押された部分がスタンプの色に変わり，下に隠れた色は全く見えなくなる．スタンプの色は付けるインクにより決定されるので，どのスタンプでも，好きな色を選ぶことが可能である．スタンプは紙から一部がはみ出た状態で押すことも可能であり，はみ出た部分は無視される．

1 つのスタンプを複数回使うことは可能である．同じスタンプを別の色に対して使ってもよい．スタンプを押すのはやや神経を使う作業なので，出来るだけスタンプを押す回数を少なくしたい．

Input

N
H1 W1
...
HN WN
C1,1C1,2C1,3C1,4
C2,1C2,2C2,3C2,4
C3,1C3,2C3,3C3,4
C4,1C4,2C4,3C4,4

N はスタンプの個数，Hi, Wi (1 ≤ i ≤ N) はそれぞれ，i 番目のスタンプの縦の長さ，横の長さを表す整数である．Ci, j (1 ≤ i ≤ 4，1 ≤ j ≤ 4) は，上から i 行目，左から j 列目のマスについて指定された絵の色を表す文字である．赤は R ，緑は G ，青は B で表される．

1 ≤ N ≤ 16，1 ≤ Hi ≤ 4，1 ≤ Wi ≤ 4 を満たす．(Hi, Wi) として同一の組は複数回現れない．

Output

絵を完成させるためにスタンプを押さなければならない最小の回数を 1 行に出力せよ．

Sample Input 1

2
4 4
1 1
RRRR
RRGR
RBRR
RRRR

Sample Output 1

3

Sample Input 2

1
2 3
RRGG
BRGG
BRRR
BRRR

Sample Output 2

5

Solution in JAVA (judge verdict: Memory Limit Exceeded)

import java.awt.geom.Point2D;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;

public class Main {
  final static int INF = 1 << 28;
  final static long MOD = 1_000_000_007;
  final static double EPS = 1e-9;
  final static double GOLDEN_RATIO = (1.0 + Math.sqrt(5)) / 2.0;
  Scanner sc = new Scanner(System.in);

  public static void main(String[] args) {
    new Main().run();
  }

  class Board {
    int cost;
    int diff;
    int diffdiff;
    int enc;
    int index;
  }

  int diff(int[][] ans, int[][] field) {
    int cnt = 0;
    for (int i = 0; i < 4; ++i) {
      for (int j = 0; j < 4; ++j) {
        if (ans[i][j] != field[i][j]) {
          ++cnt;
        }
      }
    }
    return cnt;
  }

  int encode(int[][] field) {
    int sum = 0;
    int shift = 0;
    for (int i = 0; i < 4; ++i) {
      for (int j = 0; j < 4; ++j) {
        sum |= field[i][j] << shift;
        shift += 2;
      }
    }
    return sum;
  }

  int[][] decode(int bits) {
    int[][] field = new int[4][4];
    int shift = 0;
    for (int i = 0; i < 4; ++i) {
      for (int j = 0; j < 4; ++j) {
        int v = (bits >> shift) & 0b11;
        field[i][j] = v;
        shift += 2;
      }
    }
    return field;
  }

  int[][] copy(int[][] f) {
    int[][] g = new int[4][4];
    for (int i = 0; i < 4; ++i) {
      for (int j = 0; j < 4; ++j) {
        g[i][j] = f[i][j];
      }
    }
    return g;
  }

  boolean apply(int h, int w, int c, int[][] f, int x, int y, int[][] ans) {
    boolean flag = false;
    for (int i = 0; i < h; ++i) {
      for (int j = 0; j < w; ++j) {
        int nx = x + i;
        int ny = y + j;
        if (nx < 0 || 4 <= nx || ny < 0 || 4 <= ny) {
          continue;
        }
        if (f[ny][nx] == c) {
          continue;
        }
        if (ans[ny][nx] == f[ny][nx]) {
          return false;
        }
        f[ny][nx] = c;
        flag |= ans[ny][nx] == c;
      }
    }
    return flag;
  }

  class Rect {
    int h, w;
  }

  void run() {
    int n = ni();
    int[] h = new int[n];
    int[] w = new int[n];
    ArrayList<Rect> list = new ArrayList<>();
    for (int i = 0; i < n; ++i) {
      h[i] = ni();
      w[i] = ni();
      Rect r = new Rect();
      r.h = h[i];
      r.w = w[i];
      list.add(r);
    }
    list.sort(Comparator.comparingInt((Rect r) -> r.h * r.w).reversed());
    for (int i = 0; i < n; ++i) {
      Rect r = list.get(i);
      h[i] = r.h;
      w[i] = r.w;
    }
    int[] map = new int[256];
    map['R'] = 1;
    map['G'] = 2;
    map['B'] = 3;
    int[][] ans = new int[4][4];
    for (int i = 0; i < 4; ++i) {
      String str = sc.next();
      for (int j = 0; j < 4; ++j) {
        ans[i][j] = map[str.charAt(j)];
      }
    }
    long encodedAns = encode(ans);
    Board atom = new Board();
    atom.cost = 0;
    atom.enc = 0;
    atom.diff = diff(ans, new int[4][4]);
    atom.diffdiff = 0;
    atom.index = 0;
    PriorityQueue<Board> queue = new PriorityQueue<>((a, b) -> {
      if (a.diff != b.diff) {
        return a.diff - b.diff;
      }
      if (a.diffdiff != b.diffdiff) {
        return a.diffdiff - b.diffdiff;
      }
      return -(a.index - b.index);
    });
    queue.add(atom);
    HashSet<Integer> set = new HashSet<>();
    int min = INF;
    while (queue.size() > 0) {
      Board board = queue.poll();
      int[][] bf = decode(board.enc);
      if (encodedAns == board.enc) {
        min = Math.min(min, board.cost);
        break;
      }
      if (set.contains(board.enc)) {
        continue;
      }
      set.add(board.enc);
      for (int i = board.index; i < n; ++i) {
        for (int y = -3; y <= 3; ++y) {
          for (int x = -3; x <= 3; ++x) {
            for (int c = 1; c <= 3; ++c) {
              int[][] field = copy(bf);
              boolean flag = apply(h[i], w[i], c, field, x, y, ans);
              if (!flag) {
                field = null;
                continue;
              }
              Board next = new Board();
              next.enc = encode(field);
              next.cost = board.cost + 1;
              next.diff = diff(ans, field);
              next.diffdiff = next.diff - board.diff;
              next.index = i;
              queue.add(next);
              field = null;
            }
          }
        }
      }
      bf = null;
    }
    System.out.println(min);
  }

  int ni() {
    return Integer.parseInt(sc.next());
  }

  void debug(Object... os) {
    System.err.println(Arrays.deepToString(os));
  }
}