Problem:
Pattern Search

Find places where a R × C pattern is found within a H × W region. Print top-left coordinates (i, j) of sub-regions where the pattern found. The top-left and bottom-right coordinates of the region is (0, 0) and (H-1, W-1) respectively.

Input

In the first line, two integers H and W are given. In the following H lines, i-th lines of the region are given.

In the next line, two integers R and C are given. In the following R lines, i-th lines of the pattern are given.

output

For each sub-region found, print a coordinate i and j separated by a space character in a line. Print the coordinates in ascending order of the row numbers (i), or the column numbers (j) in case of a tie.

Constraints

1 ≤ H, W ≤ 1000

1 ≤ R, C ≤ 1000

The input consists of alphabetical characters and digits

Sample Input 1

4 5
00010
00101
00010
00100
3 2
10
01
10

Sample Output 1

0 3
1 2

Solution in C# (judge verdict: Accepted)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CompProgLib;

namespace ALDS1_14_C
{
    public class Program

    {
        public static void Main(string[] args)
        {
            int[] line = ReadIntAr();
            char[,] target = new char[line[0],line[1]];
            for (int i = 0 ; i < line[0] ; i++)
            {
                string item = ReadSt();
                for (int j = 0 ; j < line[1] ; j++)
                {
                    target[i,j] = item[j];
                }            
            }

            int[] line2 = ReadIntAr();
            char[,] pattern = new char[line2[0],line2[1]];
            for (int i = 0 ; i < line2[0] ; i++)
            {
                string item = ReadSt();
                for (int j = 0 ; j < line2[1] ; j++)
                {
                    pattern[i, j] = item[j];
                }
            }

            StringSearch ss = new StringSearch();
            StringBuilder sb = new StringBuilder();

            ss.PatternMatch(target, pattern, sb);
            Console.Write(sb);

        }

        static string ReadSt() { return Console.ReadLine(); }
        static int ReadInt() { return int.Parse(Console.ReadLine()); }
        static long ReadLong() { return long.Parse(Console.ReadLine()); }
        static double ReadDouble() { return double.Parse(Console.ReadLine()); }
        static string[] ReadStAr(char sep = ' ') { return Console.ReadLine().Split(sep); }
        static int[] ReadIntAr(char sep = ' ') { return Array.ConvertAll(Console.ReadLine().Split(sep), e => int.Parse(e)); }
        static long[] ReadLongAr(char sep = ' ') { return Array.ConvertAll(Console.ReadLine().Split(sep), e => long.Parse(e)); }
        static double[] ReadDoubleAr(char sep = ' ') { return Array.ConvertAll(Console.ReadLine().Split(sep), e => double.Parse(e)); }

    }

}


namespace CompProgLib
{
    public class StringSearch
    {
        
        public void PatternMatch(char[,] target, char[,] pattern, StringBuilder sb)
        {

            const long B1 = 100000007;
            const long B2 = 100000009;

            int tRowCnt = target.GetLength(0);
            int pRowCnt = pattern.GetLength(0);
            int tColCnt = target.GetLength(1);
            int pColCnt = pattern.GetLength(1);

            if (pRowCnt > tRowCnt || pColCnt > tColCnt) return;

            //パターンのハッシュ値
            long pHash = 0;
            long[] tmp = new long[pRowCnt];

            for (int i = 0 ; i < pRowCnt ; i++)
            {
                for (int j = 0 ; j < pColCnt ; j++)
                {
                    tmp[i] = tmp[i] * B1 + pattern[i, j];
                }
            }

            for (int i = 0 ; i < pRowCnt ; i++) pHash = pHash * B2 + tmp[i];

            //ターゲットのハッシュ値

            long t1 = 1;
            for (int i = 0 ; i < pColCnt ; i++) t1 *= B1;

            long t2 = 1;
            for (int i = 0 ; i < pRowCnt ; i++) t2 *= B2;

            long[,] tmp2 = new long[tRowCnt, tColCnt - pColCnt + 1];
            bool[,] matchTbl = new bool[tRowCnt - pRowCnt + 1, tColCnt - pColCnt + 1];
            for (int i = 0 ; i < tRowCnt ; i++)
            {
                long e = 0;
                for (int j = 0 ; j < pColCnt ; j++) e = e * B1 + target[i, j];

                for (int j = 0 ; j + pColCnt <= tColCnt ; j++)
                {
                    tmp2[i, j] = e;
                    if (j + pColCnt < tColCnt) e = e * B1 - t1 * target[i, j] + target[i, j + pColCnt];
                }

            }

            for (int j = 0 ; j + pColCnt <= tColCnt ; j++)
            {
                long e = 0;
                for (int i = 0 ; i < pRowCnt ; i++) e = e * B2 + tmp2[i, j];

                for (int i = 0 ; i + pRowCnt <= tRowCnt ; i++)
                {
                    if (e == pHash) matchTbl[i,j] = true;
                    if (i + pRowCnt < tRowCnt) e = e * B2 - t2 * tmp2[i, j] + tmp2[i + pRowCnt, j];
                }
            }

            for (int i = 0 ; i < matchTbl.GetLength(0) ; i++)
            {
                for (int j = 0 ; j < matchTbl.GetLength(1) ; j++)
                {
                    if (matchTbl[i, j]) sb.AppendLine(i.ToString() + " " + j.ToString());
                }
            }

        }

    }
}