Description
The starry sky in the summer night is one of the most beautiful things on this planet. People imagine that some groups of stars in the sky form so-called constellations. Formally a constellation is a group of stars that are connected together to form a figure or picture. Some well-known constellations contain striking and familiar patterns of bright stars. Examples are Orion (containing a figure of a hunter), Leo (containing bright stars outlining the form of a lion), Scorpius (a scorpion), and Crux (a cross).
In this problem, you are to find occurrences of given constellations in a starry sky. For the sake of simplicity, the starry sky is given as a N × M matrix, each cell of which is a '*' or '0' indicating a star in the corresponding position or no star, respectively. Several constellations are given as a group of T P × Q matrices. You are to report how many constellations appear in the starry sky.
Note that a constellation appears in the sky if and only the corresponding P × Q matrix exactly matches some P × Q sub-matrix in the N × M matrix.
Input
The input consists of multiple test cases. Each test case starts with a line containing five integers N, M, T, P and Q(1 ≤ N, M ≤ 1000, 1 ≤ T ≤ 100, 1 ≤ P, Q ≤ 50).
The following N lines describe the N × M matrix, each of which contains M characters '*' or '0'.
The last part of the test case describe T constellations, each of which takes P lines in the same format as the matrix describing the sky. There is a blank line preceding each constellation.
The last test case is followed by a line containing five zeros.
Output
For each test case, print a line containing the test case number( beginning with 1) followed by the number of constellations appearing in the sky.
Sample Input
3 3 2 2 2
*00
0**
*00
**
00
*0
**
3 3 2 2 2
*00
0**
*00
**
00
*0
0*
0 0 0 0 0
Sample Output
Case 1: 1
Case 2: 2
给定一个由'*'和'0'组成的,大小为N*M(N行M列)的匹配对象和T个大小为P*Q的匹配模式。输出在匹配对象中至少出现过一次的匹配模式的个数。
在《挑战程序设计竞赛(第2版)》看到的解法是二维hash,觉得很神奇,想法很妙。哈希可参考POJ3461 && LOJ#10033 Oulipo(字符串HASH)。
首先把每一行看成一个字符串,计算从每个位置开始长度为Q的字符串子串的哈希值。然后再把得到的哈希值在列方向上看成一个字符串,计算从每个位置开始长度为P的字符串子串的哈希值。
当然,在这两次哈希值的计算中,选用了不同的基数。
用tmp[i][j]表示第i行第j个位置开始长度为Q的字符串的哈希值(用来暂存)。
用hash[i][j]表示第j列第i个位置开始长度为P的(哈希串,姑且这么称呼吧,若有雷同,纯属巧合)的哈希值。
这样将匹配模式和匹配对象的比较问题转化为了哈希值的比较问题。
代码中将哈希值保存在multiset中,不选用set的原因是multiset允许保存重复值,而匹配模式有相同的可能,故采用multiset。
AC代码:
//优快云博客:https://blog.youkuaiyun.com/qq_40889820
#include<iostream>
#include<sstream>
#include<fstream>
#include<algorithm>
#include<string>
#include<cstring>
#include<iomanip>
#include<vector>
#include<cmath>
#include<ctime>
#include<stack>
#include<queue>
#include<map>
#include<set>
#define mem(a,b) memset(a,b,sizeof(a))
#define random(a,b) (rand()%(b-a+1)+a)
#define ull unsigned long long
#define Pi 3.141592654
using namespace std;
int N,M,T,P,Q;
char field[1000][1000];//匹配对象
char patterns[1000][1000];//匹配模式
ull Hash[1000][1000],tmp[1000][1000];
void compute_hash(char a[1000][1000],int n,int m)
{
const ull B1=1e9+7;
const ull B2=1e9+9;
ull t1=1;//B1的Q次方
for(int i=0;i<Q;i++) t1*=B1;
//按行方向计算哈希值
for(int i=0;i<n;i++)
{
ull e=0;
for(int j=0;j<Q;j++) e=e*B1+a[i][j];
for(int j=0;j+Q<=m;j++)
{
tmp[i][j]=e;
if(j+Q<m) e=e*B1+a[i][j+Q]-a[i][j]*t1;
}
}
ull t2=1;//B2的P次方
for(int i=0;i<P;i++) t2*=B2;
//按列方向计算哈希值
for(int j=0;j+Q<=m;j++)
{
ull e=0;
for(int i=0;i<P;i++) e=e*B2+tmp[i][j];
for(int i=0;i+P<=n;i++)
{
Hash[i][j]=e;
if(i+P<n) e=e*B2+tmp[i+P][j]-tmp[i][j]*t2;
}
}
}
int main()
{
multiset<ull> se;
int count=0;
while(cin>>N>>M>>T>>P>>Q)
{
se.clear();//注意注意
if(N==M&&N==T&&N==T&&N==P&&N==Q) break;
for(int i=0;i<N;i++)
cin>>field[i];
//将所有匹配模式的哈希值放入一个multiset中,允许元素重复,方便统计、删除元素
for(int t=1;t<=T;t++)
{
for(int i=0;i<P;i++)
cin>>patterns[i];
compute_hash(patterns,P,Q);
se.insert(Hash[0][0]);
}
//求匹配对象中相应的哈希值,若multiset中有则删去,说明有匹配模式在匹配对象中出现了
compute_hash(field,N,M);
for(int i=0;i+P<=N;i++)
for(int j=0;j+Q<=M;j++)
se.erase(Hash[i][j]);
int ans=T-se.size();
cout<<"Case "<<++count<<": "<<ans<<endl;
}
}